Merge "Fix SystemVibrator constant support checks on device without vibrator" into sc-dev
diff --git a/core/java/com/android/internal/os/AppZygoteInit.java b/core/java/com/android/internal/os/AppZygoteInit.java
index 0e83e41..f925afc 100644
--- a/core/java/com/android/internal/os/AppZygoteInit.java
+++ b/core/java/com/android/internal/os/AppZygoteInit.java
@@ -91,7 +91,9 @@
} else {
Constructor<?> ctor = cl.getConstructor();
ZygotePreload preloadObject = (ZygotePreload) ctor.newInstance();
+ Zygote.markOpenedFilesBeforePreload();
preloadObject.doPreload(appInfo);
+ Zygote.allowFilesOpenedByPreload();
}
} catch (ReflectiveOperationException e) {
Log.e(TAG, "AppZygote application preload failed for "
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 0c9dded..e4e28a9 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -500,6 +500,36 @@
}
/**
+ * Scans file descriptors in /proc/self/fd/, stores their metadata from readlink(2)/stat(2) when
+ * available. Saves this information in a global on native side, to be used by subsequent call
+ * to allowFilesOpenedByPreload(). Fatally fails if the FDs are of unsupported type and are not
+ * explicitly allowed. Ignores repeated invocations.
+ *
+ * Inspecting the FDs is more permissive than in forkAndSpecialize() because preload is invoked
+ * earlier and hence needs to allow a few open sockets. The checks in forkAndSpecialize()
+ * enforce that these sockets are closed when forking.
+ */
+ static void markOpenedFilesBeforePreload() {
+ nativeMarkOpenedFilesBeforePreload();
+ }
+
+ private static native void nativeMarkOpenedFilesBeforePreload();
+
+ /**
+ * By scanning /proc/self/fd/ determines file descriptor numbers in this process opened since
+ * the first call to markOpenedFilesBeforePreload(). These FDs are treated as 'owned' by the
+ * custom preload of the App Zygote - the app is responsible for not sharing data with its other
+ * processes using these FDs, including by lseek(2). File descriptor types and file names are
+ * not checked. Changes in FDs recorded by markOpenedFilesBeforePreload() are not expected and
+ * kill the current process.
+ */
+ static void allowFilesOpenedByPreload() {
+ nativeAllowFilesOpenedByPreload();
+ }
+
+ private static native void nativeAllowFilesOpenedByPreload();
+
+ /**
* Installs a seccomp filter that limits setresuid()/setresgid() to the passed-in range
* @param uidGidMin The smallest allowed uid/gid
* @param uidGidMax The largest allowed uid/gid
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 4a1a272..502849e 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -27,9 +27,11 @@
#include <sys/types.h>
#include <dirent.h>
+#include <algorithm>
#include <array>
#include <atomic>
#include <functional>
+#include <iterator>
#include <list>
#include <optional>
#include <sstream>
@@ -2005,6 +2007,9 @@
__builtin_unreachable();
}
+static std::set<int>* gPreloadFds = nullptr;
+static bool gPreloadFdsExtracted = false;
+
// Utility routine to fork a process from the zygote.
pid_t zygote::ForkCommon(JNIEnv* env, bool is_system_server,
const std::vector<int>& fds_to_close,
@@ -2030,9 +2035,12 @@
__android_log_close();
AStatsSocket_close();
- // If this is the first fork for this zygote, create the open FD table. If
- // it isn't, we just need to check whether the list of open files has changed
- // (and it shouldn't in the normal case).
+ // If this is the first fork for this zygote, create the open FD table,
+ // verifying that files are of supported type and allowlisted. Otherwise (not
+ // the first fork), check that the open files have not changed. Newly open
+ // files are not expected, and will be disallowed in the future. Currently
+ // they are allowed if they pass the same checks as in the
+ // FileDescriptorTable::Create() above.
if (gOpenFdTable == nullptr) {
gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, fail_fn);
} else {
@@ -2128,7 +2136,12 @@
fds_to_ignore.push_back(gSystemServerSocketFd);
}
- pid_t pid = zygote::ForkCommon(env, false, fds_to_close, fds_to_ignore, true);
+ if (gPreloadFds && gPreloadFdsExtracted) {
+ fds_to_ignore.insert(fds_to_ignore.end(), gPreloadFds->begin(), gPreloadFds->end());
+ }
+
+ pid_t pid = zygote::ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore,
+ true);
if (pid == 0) {
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities,
@@ -2265,6 +2278,10 @@
}
fds_to_ignore.push_back(gSystemServerSocketFd);
}
+ if (gPreloadFds && gPreloadFdsExtracted) {
+ fds_to_ignore.insert(fds_to_ignore.end(), gPreloadFds->begin(), gPreloadFds->end());
+ }
+
return zygote::ForkCommon(env, /* is_system_server= */ false, fds_to_close,
fds_to_ignore, is_priority_fork == JNI_TRUE, purge);
}
@@ -2568,6 +2585,35 @@
#endif // defined(__aarch64__)
}
+static void com_android_internal_os_Zygote_nativeMarkOpenedFilesBeforePreload(JNIEnv* env, jclass) {
+ // Ignore invocations when too early or too late.
+ if (gPreloadFds) {
+ return;
+ }
+
+ // App Zygote Preload starts soon. Save FDs remaining open. After the
+ // preload finishes newly open files will be determined.
+ auto fail_fn = std::bind(zygote::ZygoteFailure, env, "zygote", nullptr, _1);
+ gPreloadFds = GetOpenFds(fail_fn).release();
+}
+
+static void com_android_internal_os_Zygote_nativeAllowFilesOpenedByPreload(JNIEnv* env, jclass) {
+ // Ignore invocations when too early or too late.
+ if (!gPreloadFds || gPreloadFdsExtracted) {
+ return;
+ }
+
+ // Find the newly open FDs, if any.
+ auto fail_fn = std::bind(zygote::ZygoteFailure, env, "zygote", nullptr, _1);
+ std::unique_ptr<std::set<int>> current_fds = GetOpenFds(fail_fn);
+ auto difference = std::make_unique<std::set<int>>();
+ std::set_difference(current_fds->begin(), current_fds->end(), gPreloadFds->begin(),
+ gPreloadFds->end(), std::inserter(*difference, difference->end()));
+ delete gPreloadFds;
+ gPreloadFds = difference.release();
+ gPreloadFdsExtracted = true;
+}
+
static const JNINativeMethod gMethods[] = {
{"nativeForkAndSpecialize",
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/"
@@ -2616,6 +2662,10 @@
(void*)com_android_internal_os_Zygote_nativeSupportsTaggedPointers},
{"nativeCurrentTaggingLevel", "()I",
(void*)com_android_internal_os_Zygote_nativeCurrentTaggingLevel},
+ {"nativeMarkOpenedFilesBeforePreload", "()V",
+ (void*)com_android_internal_os_Zygote_nativeMarkOpenedFilesBeforePreload},
+ {"nativeAllowFilesOpenedByPreload", "()V",
+ (void*)com_android_internal_os_Zygote_nativeAllowFilesOpenedByPreload},
};
int register_com_android_internal_os_Zygote(JNIEnv* env) {
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 7fa627b..6f5cc53 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -52,7 +52,6 @@
static const char kFdPath[] = "/proc/self/fd";
-// static
FileDescriptorAllowlist* FileDescriptorAllowlist::Get() {
if (instance_ == nullptr) {
instance_ = new FileDescriptorAllowlist();
@@ -169,8 +168,8 @@
// Create a FileDescriptorInfo for a given file descriptor.
static FileDescriptorInfo* CreateFromFd(int fd, fail_fn_t fail_fn);
- // Checks whether the file descriptor associated with this object
- // refers to the same description.
+ // Checks whether the file descriptor associated with this object refers to
+ // the same description.
bool RefersToSameFile() const;
void ReopenOrDetach(fail_fn_t fail_fn) const;
@@ -185,8 +184,10 @@
const bool is_sock;
private:
+ // Constructs for sockets.
explicit FileDescriptorInfo(int fd);
+ // Constructs for non-socket file descriptors.
FileDescriptorInfo(struct stat stat, const std::string& file_path, int fd, int open_flags,
int fd_flags, int fs_flags, off_t offset);
@@ -204,7 +205,6 @@
DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo);
};
-// static
FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) {
struct stat f_stat;
// This should never happen; the zygote should always have the right set
@@ -465,42 +465,24 @@
}
}
-// static
+// TODO: Move the definitions here and eliminate the forward declarations. They
+// temporarily help making code reviews easier.
+static int ParseFd(dirent* dir_entry, int dir_fd);
+static std::unique_ptr<std::set<int>> GetOpenFdsIgnoring(const std::vector<int>& fds_to_ignore,
+ fail_fn_t fail_fn);
+
FileDescriptorTable* FileDescriptorTable::Create(const std::vector<int>& fds_to_ignore,
fail_fn_t fail_fn) {
- DIR* proc_fd_dir = opendir(kFdPath);
- if (proc_fd_dir == nullptr) {
- fail_fn(std::string("Unable to open directory ").append(kFdPath));
- }
-
- int dir_fd = dirfd(proc_fd_dir);
- dirent* dir_entry;
-
+ std::unique_ptr<std::set<int>> open_fds = GetOpenFdsIgnoring(fds_to_ignore, fail_fn);
std::unordered_map<int, FileDescriptorInfo*> open_fd_map;
- while ((dir_entry = readdir(proc_fd_dir)) != nullptr) {
- const int fd = ParseFd(dir_entry, dir_fd);
- if (fd == -1) {
- continue;
- }
-
- if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
- continue;
- }
-
+ for (auto fd : *open_fds) {
open_fd_map[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn);
}
-
- if (closedir(proc_fd_dir) == -1) {
- fail_fn("Unable to close directory");
- }
-
return new FileDescriptorTable(open_fd_map);
}
-void FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn) {
- std::set<int> open_fds;
-
- // First get the list of open descriptors.
+static std::unique_ptr<std::set<int>> GetOpenFdsIgnoring(const std::vector<int>& fds_to_ignore,
+ fail_fn_t fail_fn) {
DIR* proc_fd_dir = opendir(kFdPath);
if (proc_fd_dir == nullptr) {
fail_fn(android::base::StringPrintf("Unable to open directory %s: %s",
@@ -508,6 +490,7 @@
strerror(errno)));
}
+ auto result = std::make_unique<std::set<int>>();
int dir_fd = dirfd(proc_fd_dir);
dirent* dir_entry;
while ((dir_entry = readdir(proc_fd_dir)) != nullptr) {
@@ -520,14 +503,26 @@
continue;
}
- open_fds.insert(fd);
+ result->insert(fd);
}
if (closedir(proc_fd_dir) == -1) {
fail_fn(android::base::StringPrintf("Unable to close directory: %s", strerror(errno)));
}
+ return result;
+}
- RestatInternal(open_fds, fail_fn);
+std::unique_ptr<std::set<int>> GetOpenFds(fail_fn_t fail_fn) {
+ const std::vector<int> nothing_to_ignore;
+ return GetOpenFdsIgnoring(nothing_to_ignore, fail_fn);
+}
+
+void FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn) {
+ std::unique_ptr<std::set<int>> open_fds = GetOpenFdsIgnoring(fds_to_ignore, fail_fn);
+
+ // Check that the files did not change, and leave only newly opened FDs in
+ // |open_fds|.
+ RestatInternal(*open_fds, fail_fn);
}
// Reopens all file descriptors that are contained in the table.
@@ -548,6 +543,12 @@
: open_fd_map_(map) {
}
+FileDescriptorTable::~FileDescriptorTable() {
+ for (auto& it : open_fd_map_) {
+ delete it.second;
+ }
+}
+
void FileDescriptorTable::RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn) {
// ART creates a file through memfd for optimization purposes. We make sure
// there is at most one being created.
@@ -618,8 +619,7 @@
}
}
-// static
-int FileDescriptorTable::ParseFd(dirent* dir_entry, int dir_fd) {
+static int ParseFd(dirent* dir_entry, int dir_fd) {
char* end;
const int fd = strtol(dir_entry->d_name, &end, 10);
if ((*end) != '\0') {
diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h
index 14c318e..a28ebf1 100644
--- a/core/jni/fd_utils.h
+++ b/core/jni/fd_utils.h
@@ -69,6 +69,9 @@
DISALLOW_COPY_AND_ASSIGN(FileDescriptorAllowlist);
};
+// Returns the set of file descriptors currently open by the process.
+std::unique_ptr<std::set<int>> GetOpenFds(fail_fn_t fail_fn);
+
// A FileDescriptorTable is a collection of FileDescriptorInfo objects
// keyed by their FDs.
class FileDescriptorTable {
@@ -79,6 +82,14 @@
static FileDescriptorTable* Create(const std::vector<int>& fds_to_ignore,
fail_fn_t fail_fn);
+ ~FileDescriptorTable();
+
+ // Checks that the currently open FDs did not change their metadata from
+ // stat(2), readlink(2) etc. Ignores FDs from |fds_to_ignore|.
+ //
+ // Temporary: allows newly open FDs if they pass the same checks as in
+ // Create(). This will be further restricted. See TODOs in the
+ // implementation.
void Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn);
// Reopens all file descriptors that are contained in the table. Returns true
@@ -91,8 +102,6 @@
void RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn);
- static int ParseFd(dirent* e, int dir_fd);
-
// Invariant: All values in this unordered_map are non-NULL.
std::unordered_map<int, FileDescriptorInfo*> open_fd_map_;
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-gu/strings.xml b/packages/SettingsLib/BannerMessagePreference/res/values-gu/strings.xml
new file mode 100644
index 0000000..1fe4c5c
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-gu/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="accessibility_banner_message_dismiss" msgid="5272928723898304168">"છોડી દો"</string>
+</resources>
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-te/strings.xml b/packages/SettingsLib/BannerMessagePreference/res/values-te/strings.xml
new file mode 100644
index 0000000..22a6f59
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-te/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="accessibility_banner_message_dismiss" msgid="5272928723898304168">"విస్మరించు"</string>
+</resources>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
index 7d9b4d7..6acd9ff 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
@@ -33,7 +33,7 @@
android:background="?android:attr/colorPrimary"
android:theme="@style/Theme.CollapsingToolbar.Settings">
- <com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout
+ <com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/toolbar_one_line_height"
@@ -59,7 +59,7 @@
android:transitionName="shared_element_view"
app:layout_collapseMode="pin"/>
- </com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout>
+ </com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/AdjustableToolbarLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/AdjustableToolbarLayout.java
deleted file mode 100644
index 0e7e595..0000000
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/AdjustableToolbarLayout.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.settingslib.collapsingtoolbar;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.google.android.material.appbar.CollapsingToolbarLayout;
-
-/**
- * A customized version of CollapsingToolbarLayout that can apply different font size based on the
- * line count of its title.
- */
-public class AdjustableToolbarLayout extends CollapsingToolbarLayout {
-
- private static final int TOOLBAR_MAX_LINE_NUMBER = 2;
-
- public AdjustableToolbarLayout(@NonNull Context context) {
- this(context, null);
-
- }
-
- public AdjustableToolbarLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public AdjustableToolbarLayout(@NonNull Context context, @Nullable AttributeSet attrs,
- int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- initCollapsingToolbar();
- }
-
- @SuppressWarnings("RestrictTo")
- private void initCollapsingToolbar() {
- this.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- v.removeOnLayoutChangeListener(this);
- final int count = getLineCount();
- if (count > TOOLBAR_MAX_LINE_NUMBER) {
- final ViewGroup.LayoutParams lp = getLayoutParams();
- lp.height = getResources()
- .getDimensionPixelSize(R.dimen.toolbar_three_lines_height);
- setScrimVisibleHeightTrigger(
- getResources().getDimensionPixelSize(
- R.dimen.scrim_visible_height_trigger_three_lines));
- setLayoutParams(lp);
- } else if (count == TOOLBAR_MAX_LINE_NUMBER) {
- final ViewGroup.LayoutParams lp = getLayoutParams();
- lp.height = getResources()
- .getDimensionPixelSize(R.dimen.toolbar_two_lines_height);
- setScrimVisibleHeightTrigger(
- getResources().getDimensionPixelSize(
- R.dimen.scrim_visible_height_trigger_two_lines));
- setLayoutParams(lp);
- }
- }
- });
- }
-}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 4ae120a..dbcecf1 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -35,10 +35,18 @@
* A base Activity that has a collapsing toolbar layout is used for the activities intending to
* enable the collapsing toolbar function.
*/
-public class CollapsingToolbarBaseActivity extends SettingsTransitionActivity {
+public class CollapsingToolbarBaseActivity extends SettingsTransitionActivity implements
+ AppBarLayout.OnOffsetChangedListener {
+ private static final int TOOLBAR_MAX_LINE_NUMBER = 2;
+ private static final int FULLY_EXPANDED_OFFSET = 0;
+ private static final String KEY_IS_TOOLBAR_COLLAPSED = "is_toolbar_collapsed";
+
+ @Nullable
private CollapsingToolbarLayout mCollapsingToolbarLayout;
+ @Nullable
private AppBarLayout mAppBarLayout;
+ private boolean mIsToolbarCollapsed;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -48,6 +56,12 @@
super.setContentView(R.layout.collapsing_toolbar_base_layout);
mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar);
mAppBarLayout = findViewById(R.id.app_bar);
+ mAppBarLayout.addOnOffsetChangedListener(this);
+ if (savedInstanceState != null) {
+ mIsToolbarCollapsed = savedInstanceState.getBoolean(KEY_IS_TOOLBAR_COLLAPSED);
+ }
+
+ initCollapsingToolbar();
disableCollapsingToolbarLayoutScrollingBehavior();
final Toolbar toolbar = findViewById(R.id.action_bar);
@@ -107,14 +121,43 @@
return true;
}
+ @Override
+ public void onOffsetChanged(AppBarLayout appBarLayout, int offset) {
+ if (offset == FULLY_EXPANDED_OFFSET) {
+ mIsToolbarCollapsed = false;
+ } else {
+ mIsToolbarCollapsed = true;
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (isChangingConfigurations()) {
+ outState.putBoolean(KEY_IS_TOOLBAR_COLLAPSED, mIsToolbarCollapsed);
+ }
+ }
+
/**
* Returns an instance of collapsing toolbar.
*/
+ @Nullable
public CollapsingToolbarLayout getCollapsingToolbarLayout() {
return mCollapsingToolbarLayout;
}
+ /**
+ * Return an instance of app bar.
+ */
+ @Nullable
+ public AppBarLayout getAppBarLayout() {
+ return mAppBarLayout;
+ }
+
private void disableCollapsingToolbarLayoutScrollingBehavior() {
+ if (mAppBarLayout == null) {
+ return;
+ }
final CoordinatorLayout.LayoutParams params =
(CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
final AppBarLayout.Behavior behavior = new AppBarLayout.Behavior();
@@ -127,4 +170,39 @@
});
params.setBehavior(behavior);
}
+
+ @SuppressWarnings("RestrictTo")
+ private void initCollapsingToolbar() {
+ if (mCollapsingToolbarLayout == null || mAppBarLayout == null) {
+ return;
+ }
+ mCollapsingToolbarLayout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ v.removeOnLayoutChangeListener(this);
+ if (mIsToolbarCollapsed) {
+ return;
+ }
+ final int count = mCollapsingToolbarLayout.getLineCount();
+ if (count > TOOLBAR_MAX_LINE_NUMBER) {
+ final ViewGroup.LayoutParams lp = mCollapsingToolbarLayout.getLayoutParams();
+ lp.height = getResources()
+ .getDimensionPixelSize(R.dimen.toolbar_three_lines_height);
+ mCollapsingToolbarLayout.setScrimVisibleHeightTrigger(
+ getResources().getDimensionPixelSize(
+ R.dimen.scrim_visible_height_trigger_three_lines));
+ mCollapsingToolbarLayout.setLayoutParams(lp);
+ } else if (count == TOOLBAR_MAX_LINE_NUMBER) {
+ final ViewGroup.LayoutParams lp = mCollapsingToolbarLayout.getLayoutParams();
+ lp.height = getResources()
+ .getDimensionPixelSize(R.dimen.toolbar_two_lines_height);
+ mCollapsingToolbarLayout.setScrimVisibleHeightTrigger(
+ getResources().getDimensionPixelSize(
+ R.dimen.scrim_visible_height_trigger_two_lines));
+ mCollapsingToolbarLayout.setLayoutParams(lp);
+ }
+ }
+ });
+ }
}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java
index c4c74ff..faa73ff 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java
@@ -25,21 +25,31 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment;
+import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
/**
* A base fragment that has a collapsing toolbar layout for enabling the collapsing toolbar design.
*/
-public abstract class CollapsingToolbarBaseFragment extends Fragment {
+public abstract class CollapsingToolbarBaseFragment extends Fragment implements
+ AppBarLayout.OnOffsetChangedListener {
+
+ private static final int TOOLBAR_MAX_LINE_NUMBER = 2;
+ private static final int FULLY_EXPANDED_OFFSET = 0;
+ private static final String KEY_IS_TOOLBAR_COLLAPSED = "is_toolbar_collapsed";
@Nullable
private CollapsingToolbarLayout mCollapsingToolbarLayout;
+ @Nullable
+ private AppBarLayout mAppBarLayout;
@NonNull
private Toolbar mToolbar;
@NonNull
private FrameLayout mContentFrameLayout;
+ private boolean mIsToolbarCollapsed;
@Nullable
@Override
@@ -48,6 +58,13 @@
final View view = inflater.inflate(R.layout.collapsing_toolbar_base_layout, container,
false);
mCollapsingToolbarLayout = view.findViewById(R.id.collapsing_toolbar);
+ mAppBarLayout = view.findViewById(R.id.app_bar);
+ mAppBarLayout.addOnOffsetChangedListener(this);
+ if (savedInstanceState != null) {
+ mIsToolbarCollapsed = savedInstanceState.getBoolean(KEY_IS_TOOLBAR_COLLAPSED);
+ }
+ initCollapsingToolbar();
+ disableCollapsingToolbarLayoutScrollingBehavior();
mToolbar = view.findViewById(R.id.action_bar);
mContentFrameLayout = view.findViewById(R.id.content_frame);
return view;
@@ -60,6 +77,31 @@
requireActivity().setActionBar(mToolbar);
}
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (getActivity().isChangingConfigurations()) {
+ outState.putBoolean(KEY_IS_TOOLBAR_COLLAPSED, mIsToolbarCollapsed);
+ }
+ }
+
+ @Override
+ public void onOffsetChanged(AppBarLayout appBarLayout, int offset) {
+ if (offset == FULLY_EXPANDED_OFFSET) {
+ mIsToolbarCollapsed = false;
+ } else {
+ mIsToolbarCollapsed = true;
+ }
+ }
+
+ /**
+ * Return an instance of app bar.
+ */
+ @Nullable
+ public AppBarLayout getAppBarLayout() {
+ return mAppBarLayout;
+ }
+
/**
* Return the collapsing toolbar layout.
*/
@@ -75,4 +117,56 @@
public FrameLayout getContentFrameLayout() {
return mContentFrameLayout;
}
+
+ private void disableCollapsingToolbarLayoutScrollingBehavior() {
+ if (mAppBarLayout == null) {
+ return;
+ }
+ final CoordinatorLayout.LayoutParams params =
+ (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
+ final AppBarLayout.Behavior behavior = new AppBarLayout.Behavior();
+ behavior.setDragCallback(
+ new AppBarLayout.Behavior.DragCallback() {
+ @Override
+ public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
+ return false;
+ }
+ });
+ params.setBehavior(behavior);
+ }
+
+ @SuppressWarnings("RestrictTo")
+ private void initCollapsingToolbar() {
+ if (mCollapsingToolbarLayout == null || mAppBarLayout == null) {
+ return;
+ }
+ mCollapsingToolbarLayout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ v.removeOnLayoutChangeListener(this);
+ if (mIsToolbarCollapsed) {
+ return;
+ }
+ final int count = mCollapsingToolbarLayout.getLineCount();
+ if (count > TOOLBAR_MAX_LINE_NUMBER) {
+ final ViewGroup.LayoutParams lp = mCollapsingToolbarLayout.getLayoutParams();
+ lp.height = getResources()
+ .getDimensionPixelSize(R.dimen.toolbar_three_lines_height);
+ mCollapsingToolbarLayout.setScrimVisibleHeightTrigger(
+ getResources().getDimensionPixelSize(
+ R.dimen.scrim_visible_height_trigger_three_lines));
+ mCollapsingToolbarLayout.setLayoutParams(lp);
+ } else if (count == TOOLBAR_MAX_LINE_NUMBER) {
+ final ViewGroup.LayoutParams lp = mCollapsingToolbarLayout.getLayoutParams();
+ lp.height = getResources()
+ .getDimensionPixelSize(R.dimen.toolbar_two_lines_height);
+ mCollapsingToolbarLayout.setScrimVisibleHeightTrigger(
+ getResources().getDimensionPixelSize(
+ R.dimen.scrim_visible_height_trigger_two_lines));
+ mCollapsingToolbarLayout.setLayoutParams(lp);
+ }
+ }
+ });
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 76f9fe7..93166f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2482,7 +2482,8 @@
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int intrinsicBefore = getIntrinsicHeight();
super.onLayout(changed, left, top, right, bottom);
- if (intrinsicBefore != getIntrinsicHeight() && intrinsicBefore != 0) {
+ if (intrinsicBefore != getIntrinsicHeight()
+ && (intrinsicBefore != 0 || getActualHeight() > 0)) {
notifyHeightChanged(true /* needsAnimation */);
}
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 4a68b76..c842ff1 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -955,8 +955,7 @@
continue;
} else if (isApexSessionFailed(apexSession)) {
hasFailedApexSession = true;
- String errorMsg = "APEX activation failed. Check logcat messages from apexd "
- + "for more information.";
+ String errorMsg = "APEX activation failed. " + apexSession.errorMessage;
if (!TextUtils.isEmpty(apexSession.crashingNativeProcess)) {
prepareForLoggingApexdRevert(session, apexSession.crashingNativeProcess);
errorMsg = "Session reverted due to crashing native process: "
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index c510603..bc2556a 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -221,6 +221,9 @@
// transaction from the global transaction.
private final SurfaceControl.Transaction mDisplayTransaction;
+ // The tag for the token to put root tasks on the displays to sleep.
+ private static final String DISPLAY_OFF_SLEEP_TOKEN_TAG = "Display-off";
+
/** The token acquirer to put root tasks on the displays to sleep */
final ActivityTaskManagerInternal.SleepTokenAcquirer mDisplayOffTokenAcquirer;
@@ -450,7 +453,7 @@
mService = service.mAtmService;
mTaskSupervisor = mService.mTaskSupervisor;
mTaskSupervisor.mRootWindowContainer = this;
- mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl("Display-off");
+ mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl(DISPLAY_OFF_SLEEP_TOKEN_TAG);
}
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
@@ -2654,12 +2657,14 @@
Slog.d(TAG, "Remove non-exist sleep token: " + token + " from " + Debug.getCallers(6));
}
mSleepTokens.remove(token.mHashKey);
-
final DisplayContent display = getDisplayContent(token.mDisplayId);
if (display != null) {
display.mAllSleepTokens.remove(token);
if (display.mAllSleepTokens.isEmpty()) {
mService.updateSleepIfNeededLocked();
+ if (token.mTag.equals(DISPLAY_OFF_SLEEP_TOKEN_TAG)) {
+ display.mSkipAppTransitionAnimation = true;
+ }
}
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index 7d628be..68570ff 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -286,6 +286,7 @@
ApexSessionInfo activationFailed = new ApexSessionInfo();
activationFailed.sessionId = 1543;
activationFailed.isActivationFailed = true;
+ activationFailed.errorMessage = "Failed for test";
ApexSessionInfo staged = new ApexSessionInfo();
staged.sessionId = 101;
@@ -309,8 +310,8 @@
assertThat(apexSession1.getErrorCode())
.isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
- assertThat(apexSession1.getErrorMessage()).isEqualTo("APEX activation failed. Check logcat "
- + "messages from apexd for more information.");
+ assertThat(apexSession1.getErrorMessage()).isEqualTo("APEX activation failed. "
+ + "Failed for test");
assertThat(apexSession2.getErrorCode())
.isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
index c679d04..c563e06 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -52,6 +52,7 @@
data: [
":com.android.apex.apkrollback.test_v1",
":com.android.apex.cts.shim.v2_prebuilt",
+ ":StagedInstallTestApexV2_WrongSha",
":TestAppAv1",
],
test_suites: ["general-tests"],
diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
index e633c87..6a62304 100644
--- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@@ -179,6 +179,26 @@
assertThat(info.isStagedSessionFailed()).isTrue();
}
+ @Test
+ public void testApexActivationFailureIsCapturedInSession_Commit() throws Exception {
+ int sessionId = Install.single(TestApp.Apex1).setStaged().commit();
+ assertSessionReady(sessionId);
+ storeSessionId(sessionId);
+ }
+
+ @Test
+ public void testApexActivationFailureIsCapturedInSession_Verify() throws Exception {
+ int sessionId = retrieveLastSessionId();
+ assertSessionFailedWithMessage(sessionId, "has unexpected SHA512 hash");
+ }
+
+ private static void assertSessionFailedWithMessage(int sessionId, String msg) {
+ assertSessionState(sessionId, (session) -> {
+ assertThat(session.isStagedSessionFailed()).isTrue();
+ assertThat(session.getStagedSessionErrorMessage()).contains(msg);
+ });
+ }
+
private static void assertSessionReady(int sessionId) {
assertSessionState(sessionId,
(session) -> assertThat(session.isStagedSessionReady()).isTrue());
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index ccd63f9..5d7fdd1 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -56,6 +56,7 @@
@Rule
public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this);
private static final String SHIM_V2 = "com.android.apex.cts.shim.v2.apex";
+ private static final String APEX_WRONG_SHA = "com.android.apex.cts.shim.v2_wrong_sha.apex";
private static final String APK_A = "TestAppAv1.apk";
private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
@@ -322,6 +323,27 @@
runPhase("testFailStagedSessionIfStagingDirectoryDeleted_Verify");
}
+ @Test
+ public void testApexActivationFailureIsCapturedInSession() throws Exception {
+ // We initiate staging a normal apex update which passes pre-reboot verification.
+ // Then we replace the valid apex waiting in /data/app-staging with something
+ // that cannot be activated and reboot. The apex should fail to activate, which
+ // is what we want for this test.
+ runPhase("testApexActivationFailureIsCapturedInSession_Commit");
+ final String sessionId = getDevice().executeShellCommand(
+ "pm get-stagedsessions --only-ready --only-parent --only-sessionid").trim();
+ assertThat(sessionId).isNotEmpty();
+ // Now replace the valid staged apex with something invalid
+ getDevice().enableAdbRoot();
+ getDevice().executeShellCommand("rm /data/app-staging/session_" + sessionId + "/*");
+ final File invalidApexFile = mHostUtils.getTestFile(APEX_WRONG_SHA);
+ getDevice().pushFile(invalidApexFile,
+ "/data/app-staging/session_" + sessionId + "/base.apex");
+ getDevice().reboot();
+
+ runPhase("testApexActivationFailureIsCapturedInSession_Verify");
+ }
+
private List<String> getStagingDirectories() throws DeviceNotAvailableException {
String baseDir = "/data/app-staging";
try {