Home | History | Annotate | Download | only in setupwizardlib
      1 /*
      2  * Copyright (C) 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.car.setupwizardlib;
     18 
     19 import android.annotation.CallSuper;
     20 import android.content.Intent;
     21 import android.os.Bundle;
     22 import android.support.annotation.LayoutRes;
     23 import android.support.annotation.VisibleForTesting;
     24 import android.support.v4.app.Fragment;
     25 import android.support.v4.app.FragmentActivity;
     26 import android.view.View;
     27 
     28 import com.android.car.setupwizardlib.util.CarWizardManagerHelper;
     29 
     30 
     31 /**
     32  * Base Activity for CarSetupWizard screens that provides a variety of helper functions that make
     33  * it easier to work with the CarSetupWizardLayout and moving between Setup Wizard screens.
     34  *
     35  * <p>This activity sets an instance of {@link CarSetupWizardLayout} as the Content View.
     36  * <p>Provides helper methods like {@link #setContentFragment} and {@link #onContentFragmentSet} for
     37  * easy updating of the CarSetupWizard layout components based on the current Fragment being
     38  * displayed
     39  * <p>Provides helper methods like {@link #nextAction} and {@link #finishAction} for properly
     40  * moving to the next and previous screens in a Setup Wizard
     41  * <p>Provides setters {@link #setBackButtonVisible(boolean)} for setting CarSetupWizardLayout
     42  * component attributes
     43  */
     44 public class BaseActivity extends FragmentActivity {
     45     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     46     static final String CONTENT_FRAGMENT_TAG = "CONTENT_FRAGMENT_TAG";
     47     /**
     48      * Wizard Manager does not actually return an activity result, but if we invoke Wizard
     49      * Manager without requesting a result, the framework will choose not to issue a call to
     50      * onActivityResult with RESULT_CANCELED when navigating backward.
     51      */
     52     private static final int REQUEST_CODE_NEXT = 10000;
     53 
     54     /**
     55      * To implement a specific request code, see the following:
     56      *
     57      * <pre>{@code
     58      * static final int REQUEST_CODE_A = REQUEST_CODE_FIRST_USER;
     59      * static final int REQUEST_CODE_B = REQUEST_CODE_FIRST_USER + 1;</pre>
     60      * }
     61      */
     62     protected static final int REQUEST_CODE_FIRST_USER = 10001;
     63 
     64     private int mResultCode = RESULT_CANCELED;
     65 
     66     /**
     67      * Whether it is safe to make transactions on the
     68      * {@link androidx.fragment.app.FragmentManager}. This variable prevents a possible exception
     69      * when calling commit() on the FragmentManager.
     70      *
     71      * <p>The default value is {@code true} because it is only after
     72      * {@link #onSaveInstanceState(Bundle)} that fragment commits are not allowed.
     73      */
     74     private boolean mAllowFragmentCommits = true;
     75     private CarSetupWizardLayout mCarSetupWizardLayout;
     76     private Intent mResultData;
     77 
     78     @Override
     79     @CallSuper
     80     protected void onCreate(Bundle savedInstanceState) {
     81         super.onCreate(savedInstanceState);
     82         setContentView(R.layout.base_activity);
     83 
     84         mCarSetupWizardLayout = findViewById(R.id.car_setup_wizard_layout);
     85 
     86         mCarSetupWizardLayout.setBackButtonListener(v -> {
     87             if (!handleBackButton()) {
     88                 finish();
     89             }
     90         });
     91 
     92         /* If this activity has a saved instance and a content fragment, call onContentFragmentSet()
     93          * so the appropriate views/events are updated.
     94          */
     95         if (savedInstanceState != null && getContentFragment() != null) {
     96             onContentFragmentSet(getContentFragment());
     97         }
     98 
     99         resetPrimaryToolbarButtonOnCLickListener();
    100         resetSecondaryToolbarButtonOnCLickListener();
    101     }
    102 
    103     @Override
    104     @CallSuper
    105     protected void onStart() {
    106         super.onStart();
    107         // Fragment commits are not allowed once the Activity's state has been saved. Once
    108         // onStart() has been called, the FragmentManager should now allow commits.
    109         mAllowFragmentCommits = true;
    110     }
    111 
    112     @Override
    113     @CallSuper
    114     protected void onSaveInstanceState(Bundle outState) {
    115         // A transaction can only be committed with this method prior to its containing activity
    116         // saving its state.
    117         mAllowFragmentCommits = false;
    118         super.onSaveInstanceState(outState);
    119     }
    120 
    121     // Content Fragment accessors
    122 
    123     /**
    124      * Sets the content fragment and adds it to the fragment backstack.
    125      */
    126     @CallSuper
    127     protected void setContentFragmentWithBackstack(Fragment fragment) {
    128         if (mAllowFragmentCommits) {
    129             getSupportFragmentManager().beginTransaction()
    130                     .replace(R.id.car_setup_wizard_layout, fragment, CONTENT_FRAGMENT_TAG)
    131                     .addToBackStack(null)
    132                     .commit();
    133             getSupportFragmentManager().executePendingTransactions();
    134             onContentFragmentSet(getContentFragment());
    135         }
    136     }
    137 
    138     /**
    139      * Returns the fragment that is currently being displayed as the content view.
    140      */
    141     @CallSuper
    142     protected Fragment getContentFragment() {
    143         return getSupportFragmentManager().findFragmentByTag(CONTENT_FRAGMENT_TAG);
    144     }
    145 
    146     /**
    147      * Sets the fragment that will be shown as the main content of this Activity.
    148      */
    149     @CallSuper
    150     protected void setContentFragment(Fragment fragment) {
    151         if (mAllowFragmentCommits) {
    152             getSupportFragmentManager().beginTransaction()
    153                     .setCustomAnimations(
    154                             android.R.animator.fade_in,
    155                             android.R.animator.fade_out,
    156                             android.R.animator.fade_in,
    157                             android.R.animator.fade_out)
    158                     .replace(R.id.car_setup_wizard_layout, fragment, CONTENT_FRAGMENT_TAG)
    159                     .commitNow();
    160             onContentFragmentSet(getContentFragment());
    161         }
    162     }
    163 
    164     /**
    165      * Pops the top Fragment from the Fragment backstack (immediately executing the transaction) and
    166      * then updates the CarSetupWizardLayout toolbar for the current fragment.
    167      *
    168      * @return {@code true} if a fragment was popped.
    169      */
    170     @CallSuper
    171     protected boolean popBackStackImmediate() {
    172         if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
    173             getSupportFragmentManager().popBackStackImmediate();
    174             onContentFragmentSet(getContentFragment());
    175             return true;
    176         }
    177         return false;
    178     }
    179 
    180     /**
    181      * Method to be overwritten by subclasses wanting to perform any additional actions when the
    182      * content fragment is set (usually via adding or popping from fragment backstack). For example,
    183      * the CarSetupWizardLayout toolbar usually needs to be changed when the fragment changes.
    184      */
    185     protected void onContentFragmentSet(Fragment fragment) {
    186 
    187     }
    188 
    189     /**
    190      * Sets the layout view that will be shown as the main content of this Activity. Should be used
    191      * when the activity does not hold a fragment.
    192      */
    193     @CallSuper
    194     protected View setContentLayout(@LayoutRes int id) {
    195         return getLayoutInflater().inflate(id, mCarSetupWizardLayout);
    196     }
    197 
    198     /**
    199      * Method to be overwritten by subclasses wanting to implement their own back behavior.
    200      * Default behavior is to pop a fragment off of the backstack if one exists, otherwise call
    201      * finish()
    202      *
    203      * @return {@code true} whether to call finish()
    204      */
    205     protected boolean handleBackButton() {
    206         return popBackStackImmediate();
    207     }
    208 
    209     /**
    210      * Called when nextAction has been invoked, should be overridden on derived class when it is
    211      * needed perform work when nextAction has been invoked.
    212      */
    213     protected void onNextActionInvoked() {
    214     }
    215 
    216     /**
    217      * Moves to the next Activity in the SetupWizard flow.
    218      */
    219     protected void nextAction(int resultCode) {
    220         nextAction(resultCode, null);
    221     }
    222 
    223     /**
    224      * Moves to the next Activity in the SetupWizard flow, and save the intent data.
    225      */
    226     protected void nextAction(int resultCode, Intent data) {
    227         if (resultCode == RESULT_CANCELED) {
    228             throw new IllegalArgumentException("Cannot call nextAction with RESULT_CANCELED");
    229         }
    230         onNextActionInvoked();
    231         setResultCode(resultCode, data);
    232         Intent nextIntent =
    233                 CarWizardManagerHelper.getNextIntent(getIntent(), mResultCode, mResultData);
    234         startActivityForResult(nextIntent, REQUEST_CODE_NEXT);
    235     }
    236 
    237     /**
    238      * Method for finishing an action. The default behavior is to close out the screen and
    239      * go back to the previous one.
    240      */
    241     protected void finishAction() {
    242         finishAction(RESULT_CANCELED);
    243     }
    244 
    245     /**
    246      * Method for finishing an action with a non-default result code. This is a convenience
    247      * method to replace nextAction(resultCode); finish();
    248      */
    249     protected void finishAction(int resultCode) {
    250         finishAction(resultCode, null);
    251     }
    252 
    253     /**
    254      * Convenience method for nextAction(resultCode, data); finish();
    255      */
    256     protected void finishAction(int resultCode, Intent data) {
    257         if (resultCode != RESULT_CANCELED) {
    258             nextAction(resultCode, data);
    259         }
    260         finish();
    261     }
    262 
    263     /**
    264      * Method to retrieve resultCode saved via {@link #setResultCode(int, Intent)}
    265      */
    266     protected int getResultCode() {
    267         return mResultCode;
    268     }
    269 
    270     /**
    271      * Use instead of {@link #setResult(int, Intent)} so that the resultCode
    272      * and data can be referenced later
    273      */
    274     protected void setResultCode(int resultCode, Intent data) {
    275         mResultCode = resultCode;
    276         mResultData = data;
    277         setResult(resultCode, data);
    278     }
    279 
    280     /**
    281      * Use instead of {@link #setResult(int)} so that the resultCode can be referenced later
    282      */
    283     protected void setResultCode(int resultCode) {
    284         setResultCode(resultCode, getResultData());
    285     }
    286 
    287     /**
    288      * Method to retrieve intent data saved via {@link #setResultCode(int, Intent)}
    289      */
    290     protected Intent getResultData() {
    291         return mResultData;
    292     }
    293 
    294 
    295     // CarSetupWizardLayout Accessors
    296 
    297     /**
    298      * Sets whether the back button is visible. If this value is {@code true}, clicking the
    299      * button will take the user back to the previous screen in the setup flow.
    300      */
    301     protected void setBackButtonVisible(boolean visible) {
    302         mCarSetupWizardLayout.setBackButtonVisible(visible);
    303     }
    304 
    305     /**
    306      * Sets whether the toolbar title is visible.
    307      */
    308     protected void setToolbarTitleVisible(boolean visible) {
    309         mCarSetupWizardLayout.setToolbarTitleVisible(visible);
    310     }
    311 
    312     /**
    313      * Sets the text for the toolbar title.
    314      */
    315     protected void setToolbarTitleText(String text) {
    316         mCarSetupWizardLayout.setToolbarTitleText(text);
    317     }
    318 
    319     /**
    320      * Sets whether the primary continue button is visible.
    321      */
    322     protected void setPrimaryToolbarButtonVisible(boolean visible) {
    323         mCarSetupWizardLayout.setPrimaryToolbarButtonVisible(visible);
    324     }
    325 
    326     /**
    327      * Sets whether the primary continue button is enabled. If this value is {@code true},
    328      * clicking the button will take the user to the next screen in the setup flow.
    329      */
    330     protected void setPrimaryToolbarButtonEnabled(boolean enabled) {
    331         mCarSetupWizardLayout.setPrimaryToolbarButtonEnabled(enabled);
    332     }
    333 
    334     /**
    335      * Sets the text of the primary continue button.
    336      */
    337     protected void setPrimaryToolbarButtonText(String text) {
    338         mCarSetupWizardLayout.setPrimaryToolbarButtonText(text);
    339     }
    340 
    341     /**
    342      * Sets whether the primary button is displayed as a flat or raised button.
    343      */
    344     protected void setPrimaryToolbarButtonFlat(boolean flat) {
    345         mCarSetupWizardLayout.setPrimaryToolbarButtonFlat(flat);
    346     }
    347 
    348     /**
    349      * Sets the primary button onClick behavior to a custom method.
    350      *
    351      * <p>NOTE: This will overwrite the primary tool bar button's default action to call
    352      * {@link #nextAction} with RESULT_OK.
    353      */
    354     protected void setPrimaryToolbarButtonOnClickListener(View.OnClickListener listener) {
    355         mCarSetupWizardLayout.setPrimaryToolbarButtonListener(listener);
    356     }
    357 
    358     /**
    359      * Reset's the primary toolbar button's on click listener to call {@link #nextAction} with
    360      * RESULT_OK
    361      */
    362     protected void resetPrimaryToolbarButtonOnCLickListener() {
    363         setPrimaryToolbarButtonOnClickListener(v -> nextAction(RESULT_OK));
    364     }
    365 
    366 
    367     /**
    368      * Sets whether the secondary continue button is visible.
    369      */
    370     protected void setSecondaryToolbarButtonVisible(boolean visible) {
    371         mCarSetupWizardLayout.setSecondaryToolbarButtonVisible(visible);
    372     }
    373 
    374     /**
    375      * Sets whether the secondary continue button is enabled. If this value is {@code true},
    376      * clicking the button will take the user to the next screen in the setup flow.
    377      */
    378     protected void setSecondaryToolbarButtonEnabled(boolean enabled) {
    379         mCarSetupWizardLayout.setSecondaryToolbarButtonEnabled(enabled);
    380     }
    381 
    382     /**
    383      * Sets the text of the secondary continue button.
    384      */
    385     protected void setSecondaryToolbarButtonText(String text) {
    386         mCarSetupWizardLayout.setSecondaryToolbarButtonText(text);
    387     }
    388 
    389     /**
    390      * Sets the secondary button onClick behavior to a custom method.
    391      *
    392      * <p>NOTE: This will overwrite the secondary tool bar button's default action to call
    393      * {@link #nextAction} with RESULT_OK.
    394      */
    395     protected void setSecondaryToolbarButtonOnClickListener(View.OnClickListener listener) {
    396         mCarSetupWizardLayout.setSecondaryToolbarButtonListener(listener);
    397     }
    398 
    399     /**
    400      * Reset's the secondary toolbar button's on click listener to call {@link #nextAction} with
    401      * RESULT_OK
    402      */
    403     protected void resetSecondaryToolbarButtonOnCLickListener() {
    404         setSecondaryToolbarButtonOnClickListener(v -> nextAction(RESULT_OK));
    405     }
    406 
    407     /**
    408      * Adds elevation to the title bar in order to produce a drop shadow.
    409      */
    410     protected void addElevationToTitleBar(boolean animate) {
    411         mCarSetupWizardLayout.addElevationToTitleBar(animate);
    412     }
    413 
    414     /**
    415      * Removes the elevation from the title bar using an animation.
    416      */
    417     protected void removeElevationFromTitleBar(boolean animate) {
    418         mCarSetupWizardLayout.removeElevationFromTitleBar(animate);
    419     }
    420 
    421     /**
    422      * Sets whether the progress bar is visible.
    423      */
    424     protected void setProgressBarVisible(boolean visible) {
    425         mCarSetupWizardLayout.setProgressBarVisible(visible);
    426     }
    427 
    428     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    429     boolean getAllowFragmentCommits() {
    430         return mAllowFragmentCommits;
    431     }
    432 
    433     protected CarSetupWizardLayout getCarSetupWizardLayout() {
    434         return mCarSetupWizardLayout;
    435     }
    436 }
    437