ComponentActivity.java

/*
 * Copyright 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 androidx.activity;

import android.annotation.SuppressLint;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.Window;

import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.GenericLifecycleObserver;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import androidx.lifecycle.ReportFragment;
import androidx.lifecycle.ViewModelStore;
import androidx.lifecycle.ViewModelStoreOwner;

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Base class for activities that enables composition of higher level components.
 * <p>
 * Rather than all functionality being built directly into this class, only the minimal set of
 * lower level building blocks are included. Higher level components can then be used as needed
 * without enforcing a deep Activity class hierarchy or strong coupling between components.
 */
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner {

    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }

    private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);

    // Lazily recreated from NonConfigurationInstances by getViewModelStore()
    private ViewModelStore mViewModelStore;

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final CopyOnWriteArrayList<LifecycleAwareOnBackPressedCallback> mOnBackPressedCallbacks =
            new CopyOnWriteArrayList<>();

    public ComponentActivity() {
        Lifecycle lifecycle = getLifecycle();
        //noinspection ConstantConditions
        if (lifecycle == null) {
            throw new IllegalStateException("getLifecycle() returned null in ComponentActivity's "
                    + "constructor. Please make sure you are lazily constructing your Lifecycle "
                    + "in the first call to getLifecycle() rather than relying on field "
                    + "initialization.");
        }
        if (Build.VERSION.SDK_INT >= 19) {
            getLifecycle().addObserver(new GenericLifecycleObserver() {
                @Override
                public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
                    if (event == Lifecycle.Event.ON_STOP) {
                        Window window = getWindow();
                        final View decor = window != null ? window.peekDecorView() : null;
                        if (decor != null) {
                            decor.cancelPendingInputEvents();
                        }
                    }
                }
            });
        }
        getLifecycle().addObserver(new GenericLifecycleObserver() {
            @Override
            public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
    }

    @Override
    @SuppressWarnings("RestrictedApi")
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ReportFragment.injectIfNeededIn(this);
    }

    @SuppressLint("RestrictedApi")
    @CallSuper
    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        Lifecycle lifecycle = getLifecycle();
        if (lifecycle instanceof LifecycleRegistry) {
            ((LifecycleRegistry) lifecycle).markState(Lifecycle.State.CREATED);
        }
        super.onSaveInstanceState(outState);
    }

    /**
     * Retain all appropriate non-config state.  You can NOT
     * override this yourself!  Use a {@link androidx.lifecycle.ViewModel} if you want to
     * retain your own non config state.
     */
    @Override
    @Nullable
    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            // No one called getViewModelStore(), so see if there was an existing
            // ViewModelStore from our last NonConfigurationInstance
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                viewModelStore = nc.viewModelStore;
            }
        }

        if (viewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }

    /**
     * Use this instead of {@link #onRetainNonConfigurationInstance()}.
     * Retrieve later with {@link #getLastCustomNonConfigurationInstance()}.
     *
     * @deprecated Use a {@link androidx.lifecycle.ViewModel} to store non config state.
     */
    @Deprecated
    @Nullable
    public Object onRetainCustomNonConfigurationInstance() {
        return null;
    }

    /**
     * Return the value previously returned from
     * {@link #onRetainCustomNonConfigurationInstance()}.
     *
     * @deprecated Use a {@link androidx.lifecycle.ViewModel} to store non config state.
     */
    @Deprecated
    @Nullable
    public Object getLastCustomNonConfigurationInstance() {
        NonConfigurationInstances nc = (NonConfigurationInstances)
                getLastNonConfigurationInstance();
        return nc != null ? nc.custom : null;
    }

    /**
     * {@inheritDoc}
     * <p>
     * Overriding this method is no longer supported and this method will be made
     * <code>final</code> in a future version of ComponentActivity. If you do override
     * this method, you <code>must</code>:
     * <ol>
     *     <li>Return an instance of {@link LifecycleRegistry}</li>
     *     <li>Lazily initialize your LifecycleRegistry object when this is first called.
     *     Note that this method will be called in the super classes' constructor, before any
     *     field initialization or object state creation is complete.</li>
     * </ol>
     */
    @NonNull
    @Override
    public Lifecycle getLifecycle() {
        return mLifecycleRegistry;
    }

    /**
     * Returns the {@link ViewModelStore} associated with this activity
     *
     * @return a {@code ViewModelStore}
     * @throws IllegalStateException if called before the Activity is attached to the Application
     * instance i.e., before onCreate()
     */
    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

    /**
     * Called when the activity has detected the user's press of the back
     * key. Any {@link OnBackPressedCallback} added via
     * {@link #addOnBackPressedCallback(LifecycleOwner, OnBackPressedCallback)} will be given a
     * chance to handle the back button before the default behavior of
     * {@link android.app.Activity#onBackPressed()} is invoked.
     *
     * @see #addOnBackPressedCallback(LifecycleOwner, OnBackPressedCallback)
     */
    @Override
    public void onBackPressed() {
        for (OnBackPressedCallback onBackPressedCallback : mOnBackPressedCallbacks) {
            if (onBackPressedCallback.handleOnBackPressed()) {
                return;
            }
        }
        // If none of the registered OnBackPressedCallbacks handled the back button,
        // delegate to the super implementation
        super.onBackPressed();
    }

    /**
     * Add a new {@link OnBackPressedCallback}. Callbacks are invoked in order of recency, so
     * this newly added {@link OnBackPressedCallback} will be the first callback to receive a
     * callback if {@link #onBackPressed()} is called. Only if this callback returns
     * <code>false</code> from its {@link OnBackPressedCallback#handleOnBackPressed()} will any
     * previously added callback be called.
     * <p>
     * This is the equivalent of passing this {@link ComponentActivity} to
     * {@link #addOnBackPressedCallback(LifecycleOwner, OnBackPressedCallback)} and ensures that
     * the {@link OnBackPressedCallback#handleOnBackPressed()} callback will only be called
     * if this {@link ComponentActivity} is at least {@link Lifecycle.State#STARTED}. You can
     * remove the callback prior to the destruction of your activity by calling
     * {@link #removeOnBackPressedCallback(OnBackPressedCallback)}.
     *
     * @param onBackPressedCallback The callback to add
     *
     * @see #onBackPressed()
     * @see #removeOnBackPressedCallback(OnBackPressedCallback)
     */
    public void addOnBackPressedCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
        addOnBackPressedCallback(this, onBackPressedCallback);
    }

    /**
     * Add a new {@link OnBackPressedCallback}. Callbacks are invoked in order of recency, so
     * this newly added {@link OnBackPressedCallback} will be the first callback to receive a
     * callback if {@link #onBackPressed()} is called. Only if this callback returns
     * <code>false</code> from its {@link OnBackPressedCallback#handleOnBackPressed()} will any
     * previously added callback be called.
     * <p>
     * The {@link OnBackPressedCallback#handleOnBackPressed()} callback will only be called if the
     * given {@link LifecycleOwner} is at least {@link Lifecycle.State#STARTED}. When the
     * {@link LifecycleOwner} is {@link Lifecycle.State#DESTROYED destroyed}, it will automatically
     * be removed from the list of callbacks. The only time you would need to manually call
     * {@link #removeOnBackPressedCallback(OnBackPressedCallback)} is if you'd like to remove the
     * callback prior to destruction of the associated lifecycle.
     *
     * @param owner The LifecycleOwner which controls when the callback should be invoked
     * @param onBackPressedCallback The callback to add
     *
     * @see #onBackPressed()
     * @see #removeOnBackPressedCallback(OnBackPressedCallback)
     */
    public void addOnBackPressedCallback(@NonNull LifecycleOwner owner,
            @NonNull OnBackPressedCallback onBackPressedCallback) {
        Lifecycle lifecycle = owner.getLifecycle();
        if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
            // Already destroyed, nothing to do
            return;
        }
        // Add new callbacks to the front of the list so that
        // the most recently added callbacks get priority
        mOnBackPressedCallbacks.add(0, new LifecycleAwareOnBackPressedCallback(
                lifecycle, onBackPressedCallback));
    }

    /**
     * Remove a previously
     * {@link #addOnBackPressedCallback(LifecycleOwner, OnBackPressedCallback) added}
     * {@link OnBackPressedCallback} instance. The callback won't be called for any future
     * {@link #onBackPressed()} calls, but may still receive a callback if this method is called
     * during the dispatch of an ongoing {@link #onBackPressed()} call.
     * <p>
     * This call is usually not necessary as callbacks will be automatically removed when their
     * associated {@link LifecycleOwner} is {@link Lifecycle.State#DESTROYED destroyed}.
     *
     * @param onBackPressedCallback The callback to remove
     * @see #addOnBackPressedCallback(LifecycleOwner, OnBackPressedCallback)
     */
    public void removeOnBackPressedCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
        Iterator<LifecycleAwareOnBackPressedCallback> iterator =
                mOnBackPressedCallbacks.iterator();
        LifecycleAwareOnBackPressedCallback callbackToRemove = null;
        while (iterator.hasNext()) {
            LifecycleAwareOnBackPressedCallback callback = iterator.next();
            if (callback.getOnBackPressedCallback().equals(onBackPressedCallback)) {
                callbackToRemove = callback;
                break;
            }
        }
        if (callbackToRemove != null) {
            callbackToRemove.onRemoved();
            mOnBackPressedCallbacks.remove(callbackToRemove);
        }
    }

    private class LifecycleAwareOnBackPressedCallback implements
            OnBackPressedCallback,
            GenericLifecycleObserver {
        private final Lifecycle mLifecycle;
        private final OnBackPressedCallback mOnBackPressedCallback;

        LifecycleAwareOnBackPressedCallback(@NonNull Lifecycle lifecycle,
                @NonNull OnBackPressedCallback onBackPressedCallback) {
            mLifecycle = lifecycle;
            mOnBackPressedCallback = onBackPressedCallback;
            mLifecycle.addObserver(this);
        }

        Lifecycle getLifecycle() {
            return mLifecycle;
        }

        OnBackPressedCallback getOnBackPressedCallback() {
            return mOnBackPressedCallback;
        }

        @Override
        public boolean handleOnBackPressed() {
            if (mLifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
                return mOnBackPressedCallback.handleOnBackPressed();
            }
            return false;
        }

        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                synchronized (mOnBackPressedCallbacks) {
                    mLifecycle.removeObserver(this);
                    mOnBackPressedCallbacks.remove(this);
                }
            }
        }

        public void onRemoved() {
            mLifecycle.removeObserver(this);
        }
    }
}