Expose a base Activity subclass (ComponentActivity)

Provide a subclass of Activity that provides access
to a subset of composable interfaces designed around
making it easier to listen for Activity callbacks, etc
without subclassing Activity.

The initial implementation provides access to lifecycle
callbacks from LifecycleObservers by implementing the
LifecycleOwner interface.

Test: new ComponentActivityLifecycleTest
BUG: 78542858
Change-Id: I4afae7641b89587e0c0c7530594e4821e8861616
diff --git a/activity/OWNERS b/activity/OWNERS
new file mode 100644
index 0000000..21a7dab
--- /dev/null
+++ b/activity/OWNERS
@@ -0,0 +1,2 @@
[email protected]
[email protected]
diff --git a/activity/api/current.txt b/activity/api/current.txt
new file mode 100644
index 0000000..67ea111
--- /dev/null
+++ b/activity/api/current.txt
@@ -0,0 +1,9 @@
+package androidx.activity {
+
+  public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.LifecycleOwner {
+    ctor public ComponentActivity();
+    method public androidx.lifecycle.Lifecycle getLifecycle();
+  }
+
+}
+
diff --git a/activity/build.gradle b/activity/build.gradle
new file mode 100644
index 0000000..130a556
--- /dev/null
+++ b/activity/build.gradle
@@ -0,0 +1,35 @@
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+    id("kotlin-android")
+}
+
+android {
+    lintOptions {
+        fatal("UnknownNullness")
+    }
+}
+
+dependencies {
+    api(project(":annotation"))
+    api(project(":core"))
+    api(ARCH_LIFECYCLE_RUNTIME, libs.exclude_annotations_transitive)
+
+    androidTestImplementation(KOTLIN_STDLIB)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(TEST_RULES)
+    androidTestImplementation(ESPRESSO_CORE, libs.exclude_for_espresso)
+    androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+    androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+}
+
+supportLibrary {
+    name = "Activity"
+    mavenVersion = LibraryVersions.ACTIVITY
+    mavenGroup = LibraryGroups.ACTIVITY
+    inceptionYear = "2018"
+    description = "Provides the base Activity subclass and the relevant hooks to build a composable structure on top."
+}
diff --git a/activity/src/androidTest/AndroidManifest.xml b/activity/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..1279bd1
--- /dev/null
+++ b/activity/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="androidx.activity.test">
+    <uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
+
+    <application>
+        <activity android:name="androidx.activity.LifecycleComponentActivity"/>
+    </application>
+
+</manifest>
diff --git a/activity/src/androidTest/java/androidx/activity/ComponentActivityLifecycleTest.kt b/activity/src/androidTest/java/androidx/activity/ComponentActivityLifecycleTest.kt
new file mode 100644
index 0000000..4ae8419
--- /dev/null
+++ b/activity/src/androidTest/java/androidx/activity/ComponentActivityLifecycleTest.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.os.Bundle
+import androidx.lifecycle.GenericLifecycleObserver
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.test.filters.MediumTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class ComponentActivityLifecycleTest {
+
+    @get:Rule
+    val activityRule = ActivityTestRule(LifecycleComponentActivity::class.java, false, false)
+
+    @Test
+    @Throws(Throwable::class)
+    fun testLifecycleObserver() {
+        activityRule.launchActivity(null)
+        val activity = activityRule.activity
+        val activityCallbackLifecycleOwner = activity.activityCallbackLifecycleOwner
+        val lifecycleObserver = activity.lifecycleObserver
+        val countDownLatch = activity.destroyCountDownLatch
+        activityRule.finishActivity()
+        countDownLatch.await(1, TimeUnit.SECONDS)
+
+        // The Activity's lifecycle callbacks should fire first,
+        // followed by the activity's lifecycle observers
+        verify(lifecycleObserver)
+                .onStateChanged(activityCallbackLifecycleOwner, Lifecycle.Event.ON_CREATE)
+        verify(lifecycleObserver)
+                .onStateChanged(activity, Lifecycle.Event.ON_CREATE)
+        verify(lifecycleObserver)
+                .onStateChanged(activityCallbackLifecycleOwner, Lifecycle.Event.ON_START)
+        verify(lifecycleObserver)
+                .onStateChanged(activity, Lifecycle.Event.ON_START)
+        verify(lifecycleObserver)
+                .onStateChanged(activityCallbackLifecycleOwner, Lifecycle.Event.ON_RESUME)
+        verify(lifecycleObserver)
+                .onStateChanged(activity, Lifecycle.Event.ON_RESUME)
+        // Now the order reverses as things unwind
+        verify(lifecycleObserver)
+                .onStateChanged(activity, Lifecycle.Event.ON_PAUSE)
+        verify(lifecycleObserver)
+                .onStateChanged(activityCallbackLifecycleOwner, Lifecycle.Event.ON_PAUSE)
+        verify(lifecycleObserver)
+                .onStateChanged(activity, Lifecycle.Event.ON_STOP)
+        verify(lifecycleObserver)
+                .onStateChanged(activityCallbackLifecycleOwner, Lifecycle.Event.ON_STOP)
+        verify(lifecycleObserver)
+                .onStateChanged(activity, Lifecycle.Event.ON_DESTROY)
+        verify(lifecycleObserver)
+                .onStateChanged(activityCallbackLifecycleOwner, Lifecycle.Event.ON_DESTROY)
+        verifyNoMoreInteractions(lifecycleObserver)
+    }
+}
+
+class LifecycleComponentActivity : ComponentActivity() {
+    val activityCallbackLifecycleOwner: LifecycleOwner = mock(LifecycleOwner::class.java)
+    val lifecycleObserver: GenericLifecycleObserver = mock(GenericLifecycleObserver::class.java)
+    val destroyCountDownLatch = CountDownLatch(1)
+
+    init {
+        lifecycle.addObserver(lifecycleObserver)
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        lifecycleObserver.onStateChanged(activityCallbackLifecycleOwner, Lifecycle.Event.ON_CREATE)
+    }
+
+    override fun onStart() {
+        super.onStart()
+        lifecycleObserver.onStateChanged(activityCallbackLifecycleOwner, Lifecycle.Event.ON_START)
+    }
+
+    override fun onResume() {
+        super.onResume()
+        lifecycleObserver.onStateChanged(activityCallbackLifecycleOwner, Lifecycle.Event.ON_RESUME)
+    }
+
+    override fun onPause() {
+        lifecycleObserver.onStateChanged(activityCallbackLifecycleOwner, Lifecycle.Event.ON_PAUSE)
+        super.onPause()
+    }
+
+    override fun onStop() {
+        lifecycleObserver.onStateChanged(activityCallbackLifecycleOwner, Lifecycle.Event.ON_STOP)
+        super.onStop()
+    }
+
+    override fun onDestroy() {
+        lifecycleObserver.onStateChanged(activityCallbackLifecycleOwner, Lifecycle.Event.ON_DESTROY)
+        super.onDestroy()
+        destroyCountDownLatch.countDown()
+    }
+}
diff --git a/activity/src/main/AndroidManifest.xml b/activity/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..7086b5c
--- /dev/null
+++ b/activity/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest package="androidx.activity">
+</manifest>
diff --git a/activity/src/main/java/androidx/activity/ComponentActivity.java b/activity/src/main/java/androidx/activity/ComponentActivity.java
new file mode 100644
index 0000000..d4d515f
--- /dev/null
+++ b/activity/src/main/java/androidx/activity/ComponentActivity.java
@@ -0,0 +1,60 @@
+/*
+ * 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.os.Bundle;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.ReportFragment;
+
+/**
+ * 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 {
+
+    private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
+
+    @Override
+    @SuppressWarnings("RestrictedApi")
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        ReportFragment.injectIfNeededIn(this);
+    }
+
+    @CallSuper
+    @Override
+    protected void onSaveInstanceState(@NonNull Bundle outState) {
+        mLifecycleRegistry.markState(Lifecycle.State.CREATED);
+        super.onSaveInstanceState(outState);
+    }
+
+    @NonNull
+    @Override
+    public Lifecycle getLifecycle() {
+        return mLifecycleRegistry;
+    }
+}
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
index 9de4fde..1455b69 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
@@ -20,6 +20,7 @@
  * The list of maven group names of all the libraries in this project.
  */
 object LibraryGroups {
+    const val ACTIVITY = "androidx.activity"
     const val ANIMATION = "androidx.animation"
     const val ANNOTATION = "androidx.annotation"
     const val APPCOMPAT = "androidx.appcompat"
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 73986b3..debc3bf 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -101,6 +101,11 @@
     /**
      * Version code for Appcompat
      */
+    val ACTIVITY = Version("1.0.0-alpha01")
+
+    /**
+     * Version code for Appcompat
+     */
     val APPCOMPAT = Version("1.1.0-alpha01")
 
     /**
diff --git a/core/src/main/java/androidx/core/app/ComponentActivity.java b/core/src/main/java/androidx/core/app/ComponentActivity.java
index 3522c76..c9d3d92 100644
--- a/core/src/main/java/androidx/core/app/ComponentActivity.java
+++ b/core/src/main/java/androidx/core/app/ComponentActivity.java
@@ -19,19 +19,12 @@
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
 import android.app.Activity;
-import android.os.Bundle;
 import android.view.KeyEvent;
 import android.view.View;
 
-import androidx.annotation.CallSuper;
-import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.collection.SimpleArrayMap;
 import androidx.core.view.KeyEventDispatcher;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LifecycleRegistry;
-import androidx.lifecycle.ReportFragment;
 
 /**
  * Base class for activities that enables composition of higher level components.
@@ -44,7 +37,7 @@
  */
 @RestrictTo(LIBRARY_GROUP)
 public class ComponentActivity extends Activity
-        implements LifecycleOwner, KeyEventDispatcher.Component {
+        implements KeyEventDispatcher.Component {
     /**
      * Storage for {@link ExtraData} instances.
      *
@@ -53,8 +46,6 @@
     private SimpleArrayMap<Class<? extends ExtraData>, ExtraData> mExtraDataMap =
             new SimpleArrayMap<>();
 
-    private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
-
     /**
      * Store an instance of {@link ExtraData} for later retrieval by class name
      * via {@link #getExtraData}.
@@ -69,20 +60,6 @@
         mExtraDataMap.put(extraData.getClass(), extraData);
     }
 
-    @Override
-    @SuppressWarnings("RestrictedApi")
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        ReportFragment.injectIfNeededIn(this);
-    }
-
-    @CallSuper
-    @Override
-    protected void onSaveInstanceState(Bundle outState) {
-        mLifecycleRegistry.markState(Lifecycle.State.CREATED);
-        super.onSaveInstanceState(outState);
-    }
-
     /**
      * Retrieves a previously set {@link ExtraData} by class name.
      *
@@ -94,11 +71,6 @@
         return (T) mExtraDataMap.get(extraDataClass);
     }
 
-    @Override
-    public Lifecycle getLifecycle() {
-        return mLifecycleRegistry;
-    }
-
     /**
      * @hide
      */
diff --git a/fragment/api/1.0.0.ignore b/fragment/api/1.0.0.ignore
new file mode 100644
index 0000000..4676c838
--- /dev/null
+++ b/fragment/api/1.0.0.ignore
@@ -0,0 +1,2 @@
+4188d08
+
diff --git a/fragment/api/current.txt b/fragment/api/current.txt
index ec3d391..3b0a02d 100644
--- a/fragment/api/current.txt
+++ b/fragment/api/current.txt
@@ -149,7 +149,7 @@
     field public static final android.os.Parcelable.Creator<androidx.fragment.app.Fragment.SavedState> CREATOR;
   }
 
-  public class FragmentActivity extends androidx.core.app.ComponentActivity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback androidx.core.app.ActivityCompat.RequestPermissionsRequestCodeValidator androidx.lifecycle.ViewModelStoreOwner {
+  public class FragmentActivity extends androidx.activity.ComponentActivity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback androidx.core.app.ActivityCompat.RequestPermissionsRequestCodeValidator androidx.lifecycle.ViewModelStoreOwner {
     ctor public FragmentActivity();
     method public java.lang.Object getLastCustomNonConfigurationInstance();
     method public androidx.fragment.app.FragmentManager getSupportFragmentManager();
diff --git a/fragment/build.gradle b/fragment/build.gradle
index c54b07f..3a66af0 100644
--- a/fragment/build.gradle
+++ b/fragment/build.gradle
@@ -13,6 +13,7 @@
     api(project(":legacy-support-core-utils"))
     api(project(":annotation"))
     api(project(":loader"))
+    api(project(":activity"))
     api(ARCH_LIFECYCLE_VIEWMODEL, libs.exclude_annotations_transitive)
 
     androidTestImplementation(KOTLIN_STDLIB)
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/SafeTransactionInOnResumeTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/SafeTransactionInOnResumeTest.kt
index 03a00048..fc6d741 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/SafeTransactionInOnResumeTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/SafeTransactionInOnResumeTest.kt
@@ -73,7 +73,7 @@
         }
     }
 
-    override fun onSaveInstanceState(outState: Bundle?) {
+    override fun onSaveInstanceState(outState: Bundle) {
         super.onSaveInstanceState(outState)
         DialogActivity.finish()
     }
diff --git a/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java b/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
index 8747465..1a97966 100644
--- a/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
+++ b/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
@@ -34,13 +34,13 @@
 import android.view.View;
 import android.view.Window;
 
+import androidx.activity.ComponentActivity;
 import androidx.annotation.CallSuper;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.collection.SparseArrayCompat;
 import androidx.core.app.ActivityCompat;
-import androidx.core.app.ComponentActivity;
 import androidx.core.app.SharedElementCallback;
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleOwner;
@@ -297,16 +297,6 @@
     }
 
     /**
-     * Returns the Lifecycle of the provider.
-     *
-     * @return The lifecycle of the provider.
-     */
-    @Override
-    public Lifecycle getLifecycle() {
-        return super.getLifecycle();
-    }
-
-    /**
      * Perform initialization of all fragments.
      */
     @SuppressWarnings("deprecation")
diff --git a/jetifier/jetifier/core/src/main/resources/default.config b/jetifier/jetifier/core/src/main/resources/default.config
index 4066973..3e871e26 100644
--- a/jetifier/jetifier/core/src/main/resources/default.config
+++ b/jetifier/jetifier/core/src/main/resources/default.config
@@ -873,6 +873,10 @@
             "to": "ignore"
         },
         {
+            "from": "androidx/activity/(.*)",
+            "to": "ignore"
+        },
+        {
             "from": "androidx/navigation/(.*)",
             "to": "ignore"
         },
diff --git a/jetifier/jetifier/core/src/main/resources/default.generated.config b/jetifier/jetifier/core/src/main/resources/default.generated.config
index fd54d63..f5465e6 100644
--- a/jetifier/jetifier/core/src/main/resources/default.generated.config
+++ b/jetifier/jetifier/core/src/main/resources/default.generated.config
@@ -838,6 +838,10 @@
       "to": "ignore"
     },
     {
+      "from": "androidx/activity/(.*)",
+      "to": "ignore"
+    },
+    {
       "from": "androidx/navigation/(.*)",
       "to": "ignore"
     },
@@ -4144,4 +4148,4 @@
       ]
     }
   }
-}
\ No newline at end of file
+}
diff --git a/settings.gradle b/settings.gradle
index cdb847d..bcd826c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -33,6 +33,7 @@
 //
 /////////////////////////////
 
+includeProject(":activity", "activity")
 includeProject(":annotation", "annotations")
 includeProject(":animation:animation", "animation")
 includeProject(":animation:animation-testing", "animation/testing")