package waymaker.top.android; // Copyright © 2015 Michael Allan. Licence MIT. import android.content.Intent; import android.os.*; import android.view.Window; import java.util.ArrayList; import waymaker.gen.*; import waymaker.spec.*; import static waymaker.gen.ActivityLifeStage.*; /** Exploring and elaborating an ultimate way, which is the * principle activity of this {@linkplain WaykitUI waykit UI}. * * @extra waymaker.top.android.Wayranging.toIntroducePolls (boolean) * Whether to {@linkplain PollIntroducer introduce polls}. A false value is useful when testing * because the initial introduction slows the start of the application. The default is true. */ public final class Wayranging extends ActivityX implements Refreshable { private static final PolyStator stators = new PolyStator<>(); /////// { lifeStage = INITIALIZING; } // ` c r e a t i o n ```````````````````````````````````````````````````````````````````````````````` protected @Override void onCreate( final Bundle inB ) { isCreatedAnew = inB == null; assert lifeStage.compareTo(CREATING) < 0: "One creation per instance, no colliding creations"; lifeStage = CREATING; lifeStageBell.ring(); super.onCreate( inB ); // obeying API create1(); if( isCreatedAnew ) create2( null ); else { logger.info( "Restoring activity state from bundle: " + inB ); final byte[] state = inB.getByteArray( Wayranging.class.getName() ); // get state from bundle // Not following the Android convention of using the bundle to save and restore each complex // object as a whole Parcelable, complete with its references to external dependencies. Rather // restoring the complex whole as originally created using constructors and/or initializers to // inject its external dependencies. All that remains therefore is to restore the state of the // internal variables of each reconstructed object. All state is restored from a single parcel: final Parcel inP = Parcel.obtain(); try { inP.unmarshall( state, 0, state.length ); // (sic) form state into parcel inP.setDataPosition( 0 ); // (undocumented requirement) KittedPolyStatorSR.openToThread(); create2( inP ); } finally { inP.recycle(); } } create3(); lifeStage = CREATED; lifeStageBell.ring(); } private void create1() { getWindow().requestFeature( Window.FEATURE_NO_TITLE ); // no activity bar, or whatever is showing title } /** @param inP The parceled state to restore, or null to restore none, in which case the * openToThread restriction is lifted. */ @ThreadRestricted("further KittedPolyStatorSR.openToThread") // for stators.startCtorRestore private void create2( final Parcel inP ) // see Recreating an Activity [RA] { int s = inP == null? stators.leaderSize(): stators.startCtorRestore(this,inP); // Actor ID. // - - - - - - assert stators.get(s++) == actorID_stator; actorID = new BelledVariable( inP == null? null: (VotingID)AndroidXID.readUDIDOrNull(inP) ); // CtorRestore to cleanly construct with restored state // Forests. // - - - - - assert stators.get(s++) == forests_stator; forests = inP == null? new ForestCache(this): new ForestCache(inP,this); // Poll name. // - - - - - - assert stators.get(s++) == pollName_stator; pollName = new BelledNonNull( inP == null? "end": inP.readString() ); // CtorRestore to cleanly construct with restored state // - - - assert s == stators.size(); } private void create3() { forester = new Forester( this ); final boolean toIntroducePolls = Android.unnull( getIntent().getExtras() ) .getBoolean( Wayranging.class.getName() + ".toIntroducePolls", /*default*/true ); if( toIntroducePolls ) new PollIntroducer( this ); setContentView( new WayrangingV( this )); } // -------------------------------------------------------------------------------------------------- /** The identity tag of the wayranging actor, or null if there is none. * * @see #position(String,VotingID) */ public BelledVariable actorID() { return actorID;} private BelledVariable actorID; // final after create2 private static final Object actorID_stator = stators.add( new StateSaver() { public void save( final Wayranging wr, final Parcel out ) { AndroidXID.writeUDIDOrNull( wr.actorID.get(), out ); } }); /** Adds a refreshable component to this activity. The component will refresh each time the * activity refreshes, together with the other components in the order they are added. */ public void addRefreshable( final Refreshable component ) { refreshables.add( component ); } /** The forester of this wayranging activity. */ public Forester forester() { return forester; } private Forester forester; // final after create3 /** The pollar forests of this wayranging activity. */ public ForestCache forests() { return forests; } private ForestCache forests; // final after create2 private static final Object forests_stator = stators.add( new StateSaver() { public void save( final Wayranging wr, final Parcel out ) { ForestCache.stators.save( wr.forests, out ); } }); /** Answers whether this activity’s creation is a creation from scratch, as opposed to {@linkplain * #onRestoreInstanceState(Bundle) saved state}. * * @throws IllegalStateException if the life stage is less than CREATING. */ public boolean isCreatedAnew() { if( lifeStage.compareTo(CREATING) < 0 ) throw new IllegalStateException(); return isCreatedAnew; } private boolean isCreatedAnew; /** The life stage of this activity. Initially set to INITIALIZING, any subsequent change to the * return value will be signalled by the life stage bell. */ public ActivityLifeStage lifeStage() { return lifeStage; } private ActivityLifeStage lifeStage; /** A bell that rings when the life stage changes. * * @see Application.ActivityLifecycleCallbacks.html */ public Bell lifeStageBell() { return lifeStageBell; } private final ReRinger lifeStageBell = Changed.newReRinger(); /** The name of the poll on which wayranging now focuses. * * @see #position(String,VotingID) */ public BelledNonNull pollName() { return pollName;} private BelledNonNull pollName; // final after create2 private static final Object pollName_stator = stators.add( new StateSaver() { public void save( final Wayranging wr, final Parcel out ) { out.writeString( wr.pollName.get() ); } }); /** Atomically sets the poll position on which wayranging now focuses by setting, * in effect simultaneously, both the {@linkplain #pollName() poll name} * and {@linkplain #actorID() actor identity}. */ public void position( final String _pollName, final VotingID _actorID ) { final boolean p = pollName.setSilently( _pollName ); final boolean a = actorID.setSilently( _actorID ); if( p ) pollName.bell().ring(); if( a ) actorID.bell().ring(); } /** Launches an activity that returns a result to the given receiver. Use this method in preference * to its namesake alternatives, which afford no means of passing the result to the caller. * * @see startActivityForResult(Intent,int) * @see startActivityForResult(Intent,int,Bundle) */ public void startActivityForResult( final Intent request, final ActivityResultReceiver resultReceiver ) { if( startActivity_resultReceiver != null ) { throw new IllegalStateException( "Start of new activity when old is still pending" ); // implied impossible, http://developer.android.com/guide/components/tasks-and-back-stack.html } startActivity_resultReceiver = resultReceiver; startActivityForResult( request, startActivity_requestCode ); // to continue in onActivityResult } private ActivityResultReceiver startActivity_resultReceiver; /* For pending request, or null if none. Must persist by stator, as calling an activity may happen to entail save/restore of this one. */ private int startActivity_requestCode = 0; /* That of next result to be returned. Must be ≥ zero or no result will be returned; see #startActivityForResult(Intent,int,Bundle). */ static { stators.add( new Stator() { public void save( final Wayranging wr, final Parcel out ) { ParcelX.writeParcelable( wr.startActivity_resultReceiver, out ); out.writeInt( wr.startActivity_requestCode ); } public void restore( final Wayranging wr, final Parcel in ) { wr.startActivity_resultReceiver = ParcelX.readParcelable( in ); wr.startActivity_requestCode = in.readInt(); } });} /** The wayscope zoomer of this wayranging activity. */ public WayscopeZoomer wayscopeZoomer() { return wayscopeZoomer; } private final WayscopeZoomer wayscopeZoomer = new WayscopeZoomer(); static { stators.add( new Stator() { public void save( final Wayranging wr, final Parcel out ) { WayscopeZoomer.stators.save( wr.wayscopeZoomer, out ); } public void restore( final Wayranging wr, final Parcel in ) { WayscopeZoomer.stators.restore( wr.wayscopeZoomer, in ); } });} // - R e f r e s h a b l e -------------------------------------------------------------------------- /** {@inheritDoc} First clears the HTTP response cache, then delegates the call * to each {@linkplain #addRefreshable(Refreshable) refreshable component} in turn. */ public void refreshFromAllSources() { wk.clearHttpResponseCache(); // before any r below hits it for( Refreshable r: refreshables ) r.refreshFromAllSources(); } /** {@inheritDoc} Delegates the call * to each {@linkplain #addRefreshable(Refreshable) refreshable component} in turn. */ public void refreshFromLocalWayrepo() { for( Refreshable r: refreshables ) r.refreshFromLocalWayrepo(); } //// P r i v a t e ///////////////////////////////////////////////////////////////////////////////////// private static final java.util.logging.Logger logger = LoggerX.getLogger( Wayranging.class ); private final ArrayList refreshables = new ArrayList<>(); private static final WaykitUI wk = WaykitUI.i(); // - A c t i v i t y -------------------------------------------------------------------------------- @Override protected void onActivityResult( final int requestCode, final int resultCode, final Intent result ) { assert wk.isMainThread(); final ActivityResultReceiver receiver = startActivity_resultReceiver; fence: { final String expected; if( receiver == null ) expected = "*none*"; else { if( requestCode == startActivity_requestCode ) break fence; expected = Integer.toString( startActivity_requestCode ); } throw new IllegalStateException( "Expecting result from activity request " + expected + ", but received result from " + requestCode ); } startActivity_resultReceiver = null; startActivity_requestCode = MathX08.incrementExact( startActivity_requestCode ); // "Must be ≥ zero" receiver.receive( resultCode, result ); } protected @Override void onDestroy() { assert lifeStage.compareTo(DESTROYING) < 0: "One destruction per instance, no colliding destructions"; lifeStage = DESTROYING; lifeStageBell.ring(); super.onDestroy(); lifeStage = DESTROYED; lifeStageBell.ring(); } protected @Override void onSaveInstanceState( final Bundle outB ) // see Recreating an Activity [RA] { logger.info( "Saving activity state to bundle" ); super.onSaveInstanceState( outB ); // at least in order to reopen any open dialogues KittedPolyStatorSR.openToThread(); // (a) before stators.save (b) final byte[] state; final Parcel outP = Parcel.obtain(); try { stators.save( this, outP ); // save all state variables to parcel, (b) after (a) state = outP.marshall(); // (sic) form parcel into state } finally { outP.recycle(); } outB.putByteArray( Wayranging.class.getName(), state ); // put state into bundle } /////// static { stators.seal(); } } // Notes // ----- // [RA] Recreating an Activity // http://developer.android.com/training/basics/activity-lifecycle/recreating.html