| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * 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 com.android.car.setupwizardlib; |
| |
| import android.annotation.CallSuper; |
| import android.content.Intent; |
| import android.os.Bundle; |
| import android.util.Log; |
| import android.view.View; |
| |
| import androidx.annotation.LayoutRes; |
| import androidx.annotation.StyleRes; |
| import androidx.annotation.VisibleForTesting; |
| import androidx.fragment.app.Fragment; |
| import androidx.fragment.app.FragmentActivity; |
| |
| import com.android.car.setupwizardlib.util.CarDrivingStateMonitor; |
| import com.android.car.setupwizardlib.util.CarWizardManagerHelper; |
| |
| |
| /** |
| * Base Activity for CarSetupWizard screens that provides a variety of helper functions that make |
| * it easier to work with the CarSetupWizardLayout and moving between Setup Wizard screens. |
| * |
| * <p>This activity sets an instance of {@link CarSetupWizardLayout} as the Content View. |
| * <p>Provides helper methods like {@link #setContentFragment} and {@link #onContentFragmentSet} for |
| * easy updating of the CarSetupWizard layout components based on the current Fragment being |
| * displayed |
| * <p>Provides helper methods like {@link #nextAction} and {@link #finishAction} for properly |
| * moving to the next and previous screens in a Setup Wizard |
| * <p>Provides setters {@link #setBackButtonVisible(boolean)} for setting CarSetupWizardLayout |
| * component attributes |
| * |
| * @deprecated Use {@link BaseCompatActivity} or {@link BaseDesignActivity}. |
| */ |
| @Deprecated |
| public class BaseActivity extends FragmentActivity { |
| @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) |
| static final String CONTENT_FRAGMENT_TAG = "CONTENT_FRAGMENT_TAG"; |
| /** |
| * Wizard Manager does not actually return an activity result, but if we invoke Wizard |
| * Manager without requesting a result, the framework will choose not to issue a call to |
| * onActivityResult with RESULT_CANCELED when navigating backward. |
| */ |
| protected static final int REQUEST_CODE_NEXT = 10000; |
| |
| private boolean mNextActionAlreadyTriggered; |
| |
| /** |
| * To implement a specific request code, see the following: |
| * |
| * <pre>{@code |
| * static final int REQUEST_CODE_A = REQUEST_CODE_FIRST_USER; |
| * static final int REQUEST_CODE_B = REQUEST_CODE_FIRST_USER + 1;</pre> |
| * } |
| */ |
| protected static final int REQUEST_CODE_FIRST_USER = 10001; |
| |
| private int mResultCode = RESULT_CANCELED; |
| |
| /** |
| * Whether it is safe to make transactions on the |
| * {@link androidx.fragment.app.FragmentManager}. This variable prevents a possible exception |
| * when calling commit() on the FragmentManager. |
| * |
| * <p>The default value is {@code true} because it is only after |
| * {@link #onSaveInstanceState(Bundle)} that fragment commits are not allowed. |
| */ |
| private boolean mAllowFragmentCommits = true; |
| private CarSetupWizardLayout mCarSetupWizardLayout; |
| private Intent mResultData; |
| |
| @Override |
| @CallSuper |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| setContentView(R.layout.base_activity); |
| |
| mCarSetupWizardLayout = findViewById(R.id.car_setup_wizard_layout); |
| |
| mCarSetupWizardLayout.setBackButtonListener(v -> { |
| if (!handleBackButton()) { |
| finish(); |
| } |
| }); |
| |
| resetPrimaryToolbarButtonOnClickListener(); |
| resetSecondaryToolbarButtonOnClickListener(); |
| |
| /* If this activity has a saved instance and a content fragment, call onContentFragmentSet() |
| * so the appropriate views/events are updated. |
| */ |
| if (savedInstanceState != null && getContentFragment() != null) { |
| onContentFragmentSet(getContentFragment()); |
| } |
| } |
| |
| @Override |
| @CallSuper |
| protected void onStart() { |
| super.onStart(); |
| // Fragment commits are not allowed once the Activity's state has been saved. Once |
| // onStart() has been called, the FragmentManager should now allow commits. |
| mAllowFragmentCommits = true; |
| // Need to check for UX restrictions to setup wizard running and exit if they are enabled. |
| CarDrivingStateMonitor.get(this).startMonitor(); |
| } |
| |
| @Override |
| @CallSuper |
| protected void onResume() { |
| super.onResume(); |
| // Need to reset next buttons so that they can be pressed again. |
| mNextActionAlreadyTriggered = false; |
| } |
| |
| @Override |
| @CallSuper |
| protected void onPause() { |
| super.onPause(); |
| // Need this for visibility for tests. |
| } |
| |
| @Override |
| @CallSuper |
| protected void onStop() { |
| super.onStop(); |
| // Trigger a stop to the CarDrivingStateMonitor. If the monitor is restarted soon by a |
| // subsequent activity then this will do nothing so as not to thrash the monitor. |
| CarDrivingStateMonitor.get(this).stopMonitor(); |
| } |
| |
| @Override |
| @CallSuper |
| protected void onSaveInstanceState(Bundle outState) { |
| // A transaction can only be committed with this method prior to its containing activity |
| // saving its state. |
| mAllowFragmentCommits = false; |
| super.onSaveInstanceState(outState); |
| } |
| |
| // Content Fragment accessors |
| |
| /** |
| * Sets the content fragment and adds it to the fragment backstack. |
| */ |
| @CallSuper |
| protected void setContentFragmentWithBackstack(Fragment fragment) { |
| if (mAllowFragmentCommits) { |
| getSupportFragmentManager().beginTransaction() |
| .replace(R.id.car_setup_wizard_layout, fragment, CONTENT_FRAGMENT_TAG) |
| .addToBackStack(null) |
| .commit(); |
| getSupportFragmentManager().executePendingTransactions(); |
| onContentFragmentSet(getContentFragment()); |
| } |
| } |
| |
| /** |
| * Returns the fragment that is currently being displayed as the content view. |
| */ |
| @CallSuper |
| protected Fragment getContentFragment() { |
| return getSupportFragmentManager().findFragmentByTag(CONTENT_FRAGMENT_TAG); |
| } |
| |
| /** |
| * Sets the fragment that will be shown as the main content of this Activity. |
| */ |
| @CallSuper |
| protected void setContentFragment(Fragment fragment) { |
| if (mAllowFragmentCommits) { |
| getSupportFragmentManager().beginTransaction() |
| .setCustomAnimations( |
| android.R.animator.fade_in, |
| android.R.animator.fade_out, |
| android.R.animator.fade_in, |
| android.R.animator.fade_out) |
| .replace(R.id.car_setup_wizard_layout, fragment, CONTENT_FRAGMENT_TAG) |
| .commitNow(); |
| onContentFragmentSet(getContentFragment()); |
| } |
| } |
| |
| /** |
| * Pops the top Fragment from the Fragment backstack (immediately executing the transaction) and |
| * then updates the CarSetupWizardLayout toolbar for the current fragment. |
| * |
| * @return {@code true} if a fragment was popped. |
| */ |
| @CallSuper |
| protected boolean popBackStackImmediate() { |
| if (getSupportFragmentManager().getBackStackEntryCount() > 0) { |
| getSupportFragmentManager().popBackStackImmediate(); |
| onContentFragmentSet(getContentFragment()); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Method to be overwritten by subclasses wanting to perform any additional actions when the |
| * content fragment is set (usually via adding or popping from fragment backstack). For example, |
| * the CarSetupWizardLayout toolbar usually needs to be changed when the fragment changes. |
| */ |
| protected void onContentFragmentSet(Fragment fragment) { |
| |
| } |
| |
| /** |
| * Sets the layout view that will be shown as the main content of this Activity. Should be used |
| * when the activity does not hold a fragment. |
| */ |
| @CallSuper |
| protected View setContentLayout(@LayoutRes int id) { |
| return getLayoutInflater().inflate(id, mCarSetupWizardLayout); |
| } |
| |
| /** |
| * Method to be overwritten by subclasses wanting to implement their own back behavior. |
| * Default behavior is to pop a fragment off of the backstack if one exists, otherwise call |
| * finish() |
| * |
| * @return {@code true} whether to call finish() |
| */ |
| protected boolean handleBackButton() { |
| return popBackStackImmediate(); |
| } |
| |
| /** |
| * Called when nextAction has been invoked, should be overridden on derived class when it is |
| * needed perform work when nextAction has been invoked. |
| */ |
| protected void onNextActionInvoked() { |
| } |
| |
| /** |
| * Moves to the next Activity in the SetupWizard flow. |
| */ |
| protected void nextAction(int resultCode) { |
| nextAction(resultCode, null); |
| } |
| |
| /** |
| * Moves to the next Activity in the SetupWizard flow, and save the intent data. |
| */ |
| protected void nextAction(int resultCode, Intent data) { |
| if (resultCode == RESULT_CANCELED) { |
| throw new IllegalArgumentException("Cannot call nextAction with RESULT_CANCELED"); |
| } |
| setResultCode(resultCode, data); |
| if (mNextActionAlreadyTriggered) { |
| Log.v("CarSetupWizard", |
| "BaseActivity: nextAction triggered multiple times without page refresh, " |
| + "ignoring."); |
| return; |
| } |
| mNextActionAlreadyTriggered = true; |
| onNextActionInvoked(); |
| Intent nextIntent = |
| CarWizardManagerHelper.getNextIntent(getIntent(), mResultCode, mResultData); |
| startActivity(nextIntent); |
| } |
| |
| /** |
| * Method for finishing an action. The default behavior is to close out the screen and |
| * go back to the previous one. |
| */ |
| protected void finishAction() { |
| finishAction(RESULT_CANCELED); |
| } |
| |
| /** |
| * Method for finishing an action with a non-default result code. This is a convenience |
| * method to replace nextAction(resultCode); finish(); |
| */ |
| protected void finishAction(int resultCode) { |
| finishAction(resultCode, null); |
| } |
| |
| /** |
| * Convenience method for nextAction(resultCode, data); finish(); |
| */ |
| protected void finishAction(int resultCode, Intent data) { |
| if (resultCode != RESULT_CANCELED) { |
| nextAction(resultCode, data); |
| } |
| finish(); |
| } |
| |
| /** |
| * Method to retrieve resultCode saved via {@link #setResultCode(int, Intent)} |
| */ |
| protected int getResultCode() { |
| return mResultCode; |
| } |
| |
| /** |
| * Use instead of {@link #setResult(int, Intent)} so that the resultCode |
| * and data can be referenced later |
| */ |
| protected void setResultCode(int resultCode, Intent data) { |
| mResultCode = resultCode; |
| mResultData = data; |
| setResult(resultCode, data); |
| } |
| |
| /** |
| * Use instead of {@link #setResult(int)} so that the resultCode can be referenced later |
| */ |
| protected void setResultCode(int resultCode) { |
| setResultCode(resultCode, getResultData()); |
| } |
| |
| /** |
| * Method to retrieve intent data saved via {@link #setResultCode(int, Intent)} |
| */ |
| protected Intent getResultData() { |
| return mResultData; |
| } |
| |
| |
| // CarSetupWizardLayout Accessors |
| |
| /** |
| * Sets whether the back button is visible. If this value is {@code true}, clicking the |
| * button will take the user back to the previous screen in the setup flow. |
| */ |
| protected void setBackButtonVisible(boolean visible) { |
| mCarSetupWizardLayout.setBackButtonVisible(visible); |
| } |
| |
| /** |
| * Sets whether the toolbar title is visible. |
| */ |
| protected void setToolbarTitleVisible(boolean visible) { |
| mCarSetupWizardLayout.setToolbarTitleVisible(visible); |
| } |
| |
| /** |
| * Sets the text for the toolbar title. |
| */ |
| protected void setToolbarTitleText(String text) { |
| mCarSetupWizardLayout.setToolbarTitleText(text); |
| } |
| |
| /** |
| * Sets the text appearance for the toolbar title. |
| */ |
| protected void setToolbarTitleStyle(@StyleRes int style) { |
| mCarSetupWizardLayout.setToolbarTitleStyle(style); |
| } |
| |
| /** |
| * Sets whether the primary continue button is visible. |
| */ |
| protected void setPrimaryToolbarButtonVisible(boolean visible) { |
| mCarSetupWizardLayout.setPrimaryToolbarButtonVisible(visible); |
| } |
| |
| /** |
| * Sets whether the primary continue button is enabled. If this value is {@code true}, |
| * clicking the button will take the user to the next screen in the setup flow. |
| */ |
| protected void setPrimaryToolbarButtonEnabled(boolean enabled) { |
| mCarSetupWizardLayout.setPrimaryToolbarButtonEnabled(enabled); |
| } |
| |
| /** |
| * Sets the text of the primary continue button. |
| */ |
| protected void setPrimaryToolbarButtonText(String text) { |
| mCarSetupWizardLayout.setPrimaryToolbarButtonText(text); |
| } |
| |
| /** |
| * Sets whether the primary button is displayed as a flat or raised button. |
| */ |
| protected void setPrimaryToolbarButtonFlat(boolean flat) { |
| mCarSetupWizardLayout.setPrimaryToolbarButtonFlat(flat); |
| } |
| |
| /** |
| * Sets the primary button onClick behavior to a custom method. |
| * |
| * <p>NOTE: This will overwrite the primary tool bar button's default action to call |
| * {@link #nextAction} with RESULT_OK. |
| */ |
| protected void setPrimaryToolbarButtonOnClickListener(View.OnClickListener listener) { |
| mCarSetupWizardLayout.setPrimaryToolbarButtonListener(listener); |
| } |
| |
| /** |
| * Reset's the primary toolbar button's on click listener to call {@link #nextAction} with |
| * RESULT_OK |
| */ |
| protected void resetPrimaryToolbarButtonOnClickListener() { |
| setPrimaryToolbarButtonOnClickListener(v -> { |
| nextAction(RESULT_OK); |
| }); |
| |
| } |
| |
| /** |
| * Sets whether the secondary continue button is visible. |
| */ |
| protected void setSecondaryToolbarButtonVisible(boolean visible) { |
| mCarSetupWizardLayout.setSecondaryToolbarButtonVisible(visible); |
| } |
| |
| /** |
| * Sets whether the secondary continue button is enabled. If this value is {@code true}, |
| * clicking the button will take the user to the next screen in the setup flow. |
| */ |
| protected void setSecondaryToolbarButtonEnabled(boolean enabled) { |
| mCarSetupWizardLayout.setSecondaryToolbarButtonEnabled(enabled); |
| } |
| |
| /** |
| * Sets the text of the secondary continue button. |
| */ |
| protected void setSecondaryToolbarButtonText(String text) { |
| mCarSetupWizardLayout.setSecondaryToolbarButtonText(text); |
| } |
| |
| /** |
| * Sets the secondary button onClick behavior to a custom method. |
| * |
| * <p>NOTE: This will overwrite the secondary tool bar button's default action to call |
| * {@link #nextAction} with RESULT_OK. |
| */ |
| protected void setSecondaryToolbarButtonOnClickListener(View.OnClickListener listener) { |
| mCarSetupWizardLayout.setSecondaryToolbarButtonListener(listener); |
| } |
| |
| /** |
| * Reset's the secondary toolbar button's on click listener to call {@link #nextAction} with |
| * RESULT_OK |
| */ |
| protected void resetSecondaryToolbarButtonOnClickListener() { |
| setSecondaryToolbarButtonOnClickListener(v -> { |
| nextAction(RESULT_OK); |
| }); |
| } |
| |
| /** |
| * Sets whether the progress bar is visible. |
| */ |
| protected void setProgressBarVisible(boolean visible) { |
| mCarSetupWizardLayout.setProgressBarVisible(visible); |
| } |
| |
| @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) |
| boolean getAllowFragmentCommits() { |
| return mAllowFragmentCommits; |
| } |
| |
| protected CarSetupWizardLayout getCarSetupWizardLayout() { |
| return mCarSetupWizardLayout; |
| } |
| } |