Merge "Tweaks to CarContext#requestPermissions javadoc" into androidx-main
diff --git a/core/core/lint-baseline.xml b/core/core/lint-baseline.xml
index 63626a6..cbe1a6d 100644
--- a/core/core/lint-baseline.xml
+++ b/core/core/lint-baseline.xml
@@ -46,39 +46,6 @@
     </issue>
 
     <issue
-        id="BanSynchronizedMethods"
-        message="Use of synchronized methods is not recommended"
-        errorLine1="    /**"
-        errorLine2="    ^">
-        <location
-            file="src/main/java/androidx/core/widget/ContentLoadingProgressBar.java"
-            line="92"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="BanSynchronizedMethods"
-        message="Use of synchronized methods is not recommended"
-        errorLine1="    /**"
-        errorLine2="    ^">
-        <location
-            file="src/main/java/androidx/core/widget/ContentLoadingProgressBar.java"
-            line="118"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="BanSynchronizedMethods"
-        message="Use of synchronized methods is not recommended"
-        errorLine1="    private synchronized static File createFilesDir(File file) {"
-        errorLine2="    ^">
-        <location
-            file="src/main/java/androidx/core/content/ContextCompat.java"
-            line="602"
-            column="5"/>
-    </issue>
-
-    <issue
         id="BanUncheckedReflection"
         message="Calling Method.invoke without an SDK check"
         errorLine1="                    requestRelaunchActivityMethod.invoke(activityThread,"
diff --git a/core/core/src/main/java/androidx/core/content/ContextCompat.java b/core/core/src/main/java/androidx/core/content/ContextCompat.java
index 882f35b..2f54ab4 100644
--- a/core/core/src/main/java/androidx/core/content/ContextCompat.java
+++ b/core/core/src/main/java/androidx/core/content/ContextCompat.java
@@ -158,6 +158,9 @@
 
     private static final Object sLock = new Object();
 
+    // Lock that provides similar functionality to ContextImpl.mSync.
+    private static final Object sSync = new Object();
+
     private static TypedValue sTempValue;
 
     /**
@@ -599,18 +602,23 @@
         }
     }
 
-    private synchronized static File createFilesDir(File file) {
-        if (!file.exists()) {
-            if (!file.mkdirs()) {
-                if (file.exists()) {
-                    // spurious failure; probably racing with another process for this app
+    private static File createFilesDir(File file) {
+        // In the platform, all operations on Context that involve creating files (codeCacheDir,
+        // noBackupFilesDir, etc.) are synchronized on a single lock owned by the Context. So, if
+        // we lock on a single static lock owned by ContextCompat then we're a bit too broad but
+        // at least we'll provide similar guarantees.
+        synchronized (sSync) {
+            if (!file.exists()) {
+                if (file.mkdirs()) {
                     return file;
+                } else {
+                    // There used to be another check for file.exists() here, but that was a
+                    // side-effect of improper synchronization.
+                    Log.w(TAG, "Unable to create files subdir " + file.getPath());
                 }
-                Log.w(TAG, "Unable to create files subdir " + file.getPath());
-                return null;
             }
+            return file;
         }
-        return file;
     }
 
     /**
diff --git a/core/core/src/main/java/androidx/core/widget/ContentLoadingProgressBar.java b/core/core/src/main/java/androidx/core/widget/ContentLoadingProgressBar.java
index e24b6a1..2eb84ef 100644
--- a/core/core/src/main/java/androidx/core/widget/ContentLoadingProgressBar.java
+++ b/core/core/src/main/java/androidx/core/widget/ContentLoadingProgressBar.java
@@ -23,44 +23,35 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
 
 /**
  * ContentLoadingProgressBar implements a ProgressBar that waits a minimum time to be
  * dismissed before showing. Once visible, the progress bar will be visible for
  * a minimum amount of time to avoid "flashes" in the UI when an event could take
- * a largely variable time to complete (from none, to a user perceivable amount)
+ * a largely variable time to complete (from none, to a user perceivable amount).
  */
 public class ContentLoadingProgressBar extends ProgressBar {
-    private static final int MIN_SHOW_TIME = 500; // ms
-    private static final int MIN_DELAY = 500; // ms
+    private static final int MIN_SHOW_TIME_MS = 500;
+    private static final int MIN_DELAY_MS = 500;
 
+    // These fields should only be accessed on the UI thread.
     long mStartTime = -1;
-
     boolean mPostedHide = false;
-
     boolean mPostedShow = false;
-
     boolean mDismissed = false;
 
-    private final Runnable mDelayedHide = new Runnable() {
-
-        @Override
-        public void run() {
-            mPostedHide = false;
-            mStartTime = -1;
-            setVisibility(View.GONE);
-        }
+    private final Runnable mDelayedHide = () -> {
+        mPostedHide = false;
+        mStartTime = -1;
+        setVisibility(View.GONE);
     };
 
-    private final Runnable mDelayedShow = new Runnable() {
-
-        @Override
-        public void run() {
-            mPostedShow = false;
-            if (!mDismissed) {
-                mStartTime = System.currentTimeMillis();
-                setVisibility(View.VISIBLE);
-            }
+    private final Runnable mDelayedShow = () -> {
+        mPostedShow = false;
+        if (!mDismissed) {
+            mStartTime = System.currentTimeMillis();
+            setVisibility(View.VISIBLE);
         }
     };
 
@@ -93,13 +84,23 @@
      * Hide the progress view if it is visible. The progress view will not be
      * hidden until it has been shown for at least a minimum show time. If the
      * progress view was not yet visible, cancels showing the progress view.
+     * <p>
+     * This method may be called off the UI thread.
      */
-    public synchronized void hide() {
+    public void hide() {
+        // This method used to be synchronized, presumably so that it could be safely called off
+        // the UI thread; however, the referenced fields were still accessed both on and off the
+        // UI thread, e.g. not thread-safe. Now we hand-off everything to the UI thread.
+        post(this::hideOnUiThread);
+    }
+
+    @UiThread
+    private void hideOnUiThread() {
         mDismissed = true;
         removeCallbacks(mDelayedShow);
         mPostedShow = false;
         long diff = System.currentTimeMillis() - mStartTime;
-        if (diff >= MIN_SHOW_TIME || mStartTime == -1) {
+        if (diff >= MIN_SHOW_TIME_MS || mStartTime == -1) {
             // The progress spinner has been shown long enough
             // OR was not shown yet. If it wasn't shown yet,
             // it will just never be shown.
@@ -109,7 +110,7 @@
             // so put a delayed message in to hide it when its been
             // shown long enough.
             if (!mPostedHide) {
-                postDelayed(mDelayedHide, MIN_SHOW_TIME - diff);
+                postDelayed(mDelayedHide, MIN_SHOW_TIME_MS - diff);
                 mPostedHide = true;
             }
         }
@@ -118,15 +119,25 @@
     /**
      * Show the progress view after waiting for a minimum delay. If
      * during that time, hide() is called, the view is never made visible.
+     * <p>
+     * This method may be called off the UI thread.
      */
-    public synchronized void show() {
+    public void show() {
+        // This method used to be synchronized, presumably so that it could be safely called off
+        // the UI thread; however, the referenced fields were still accessed both on and off the
+        // UI thread, e.g. not thread-safe. Now we hand-off everything to the UI thread.
+        post(this::showOnUiThread);
+    }
+
+    @UiThread
+    private void showOnUiThread() {
         // Reset the start time.
         mStartTime = -1;
         mDismissed = false;
         removeCallbacks(mDelayedHide);
         mPostedHide = false;
         if (!mPostedShow) {
-            postDelayed(mDelayedShow, MIN_DELAY);
+            postDelayed(mDelayedShow, MIN_DELAY_MS);
             mPostedShow = true;
         }
     }
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 69af6e3..1ffc337 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -208,6 +208,7 @@
     docs(project(":room:room-guava"))
     docs(project(":room:room-ktx"))
     docs(project(":room:room-migration"))
+    docs(project(":room:room-paging"))
     docs(project(":room:room-runtime"))
     docs(project(":room:room-rxjava2"))
     docs(project(":room:room-rxjava3"))
diff --git a/room/OWNERS b/room/OWNERS
index 3ea2934..6d57c2e 100644
--- a/room/OWNERS
+++ b/room/OWNERS
@@ -1,5 +1,6 @@
 [email protected]
 [email protected]
 [email protected]
[email protected]
 
-per-file settings.gradle = [email protected], [email protected]
+per-file settings.gradle = [email protected]
diff --git a/room/room-paging/api/current.txt b/room/room-paging/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/room-paging/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/room-paging/api/public_plus_experimental_current.txt b/room/room-paging/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/room-paging/api/public_plus_experimental_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/room-paging/api/res-current.txt b/room/room-paging/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/room-paging/api/res-current.txt
diff --git a/room/room-paging/api/restricted_current.txt b/room/room-paging/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/room-paging/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/room-paging/build.gradle b/room/room-paging/build.gradle
new file mode 100644
index 0000000..e9645b8
--- /dev/null
+++ b/room/room-paging/build.gradle
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryType
+import androidx.build.LibraryVersions
+import androidx.build.Publish
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+    api(KOTLIN_STDLIB)
+    // Add dependencies here
+}
+
+androidx {
+    name = "Room Paging"
+    type = LibraryType.PUBLISHED_LIBRARY
+    mavenGroup = LibraryGroups.ROOM
+    inceptionYear = "2021"
+    description = "Room Paging integration"
+    publish = Publish.NONE
+}
diff --git a/room/room-paging/src/androidTest/AndroidManifest.xml b/room/room-paging/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..50ba2eb
--- /dev/null
+++ b/room/room-paging/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2021 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.room.paging.test">
+
+</manifest>
diff --git a/room/room-paging/src/main/AndroidManifest.xml b/room/room-paging/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..fa18cd8
--- /dev/null
+++ b/room/room-paging/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2021 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.room.paging">
+
+</manifest>
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index de23551..ba10801 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -529,6 +529,7 @@
 includeProject(":room:room-guava", "room/guava", [BuildType.MAIN])
 includeProject(":room:room-ktx", "room/ktx", [BuildType.MAIN])
 includeProject(":room:room-migration", "room/migration", [BuildType.MAIN])
+includeProject(":room:room-paging", "room/room-paging", [BuildType.MAIN])
 includeProject(":room:room-runtime", "room/runtime", [BuildType.MAIN])
 includeProject(":room:room-rxjava2", "room/rxjava2", [BuildType.MAIN])
 includeProject(":room:room-rxjava3", "room/rxjava3", [BuildType.MAIN])