Merge "Add State to statsd config language"
diff --git a/apct-tests/perftests/multiuser/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml
index b2a9524..893c8ca 100644
--- a/apct-tests/perftests/multiuser/AndroidManifest.xml
+++ b/apct-tests/perftests/multiuser/AndroidManifest.xml
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 32107b4..e74e4a9 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -38,8 +38,10 @@
import android.os.IBinder;
import android.os.IProgressListener;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.perftests.utils.ShellHelper;
import android.util.Log;
import android.view.WindowManagerGlobal;
@@ -85,6 +87,14 @@
private static final String DUMMY_PACKAGE_NAME = "perftests.multiuser.apps.dummyapp";
+ // Copy of UserSystemPackageInstaller whitelist mode constants.
+ private static final String PACKAGE_WHITELIST_MODE_PROP =
+ "persist.debug.user.package_whitelist_mode";
+ private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0;
+ private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0b001;
+ private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0b100;
+ private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT = -1;
+
private UserManager mUm;
private ActivityManager mAm;
private IActivityManager mIam;
@@ -442,6 +452,55 @@
}
}
+ // TODO: This is just a POC. Do this properly and add more.
+ /** Tests starting (unlocking) a newly-created profile using the user-type-pkg-whitelist. */
+ @Test
+ public void managedProfileUnlock_usingWhitelist() throws Exception {
+ assumeTrue(mHasManagedUserFeature);
+ final int origMode = getUserTypePackageWhitelistMode();
+ setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE
+ | USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST);
+
+ try {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createManagedProfile();
+ mRunner.resumeTiming();
+
+ startUserInBackground(userId);
+
+ mRunner.pauseTiming();
+ removeUser(userId);
+ mRunner.resumeTiming();
+ }
+ } finally {
+ setUserTypePackageWhitelistMode(origMode);
+ }
+ }
+ /** Tests starting (unlocking) a newly-created profile NOT using the user-type-pkg-whitelist. */
+ @Test
+ public void managedProfileUnlock_notUsingWhitelist() throws Exception {
+ assumeTrue(mHasManagedUserFeature);
+ final int origMode = getUserTypePackageWhitelistMode();
+ setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE);
+
+ try {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createManagedProfile();
+ mRunner.resumeTiming();
+
+ startUserInBackground(userId);
+
+ mRunner.pauseTiming();
+ removeUser(userId);
+ mRunner.resumeTiming();
+ }
+ } finally {
+ setUserTypePackageWhitelistMode(origMode);
+ }
+ }
+
/** Creates a new user, returning its userId. */
private int createUserNoFlags() {
return createUserWithFlags(/* flags= */ 0);
@@ -458,6 +517,10 @@
private int createManagedProfile() {
final UserInfo userInfo = mUm.createProfileForUser("TestProfile",
UserInfo.FLAG_MANAGED_PROFILE, mAm.getCurrentUser());
+ if (userInfo == null) {
+ throw new IllegalStateException("Creating managed profile failed. Most likely there is "
+ + "already a pre-existing profile on the device.");
+ }
mUsersToRemove.add(userInfo.id);
return userInfo.id;
}
@@ -627,6 +690,20 @@
}
}
+ /** Gets the PACKAGE_WHITELIST_MODE_PROP System Property. */
+ private int getUserTypePackageWhitelistMode() {
+ return SystemProperties.getInt(PACKAGE_WHITELIST_MODE_PROP,
+ USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT);
+ }
+
+ /** Sets the PACKAGE_WHITELIST_MODE_PROP System Property to the given value. */
+ private void setUserTypePackageWhitelistMode(int mode) {
+ String result = ShellHelper.runShellCommand(
+ String.format("setprop %s %d", PACKAGE_WHITELIST_MODE_PROP, mode));
+ attestFalse("Failed to set sysprop " + PACKAGE_WHITELIST_MODE_PROP + ": " + result,
+ result != null && result.contains("Failed"));
+ }
+
private void removeUser(int userId) {
try {
mUm.removeUser(userId);
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 05ff490..c79b0ca 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -74,6 +74,7 @@
"src/external/StatsPuller.cpp",
"src/external/StatsPullerManager.cpp",
"src/external/SubsystemSleepStatePuller.cpp",
+ "src/external/SurfaceflingerStatsPuller.cpp",
"src/external/TrainInfoPuller.cpp",
"src/FieldValue.cpp",
"src/guardrail/StatsdStats.cpp",
@@ -138,6 +139,7 @@
"libservices",
"libstatslog",
"libsysutils",
+ "libtimestats_proto",
"libutils",
],
}
@@ -239,6 +241,7 @@
"tests/external/IncidentReportArgs_test.cpp",
"tests/external/puller_util_test.cpp",
"tests/external/StatsPuller_test.cpp",
+ "tests/external/SurfaceflingerStatsPuller_test.cpp",
"tests/FieldValue_test.cpp",
"tests/guardrail/StatsdStats_test.cpp",
"tests/indexed_priority_queue_test.cpp",
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index f69e2d0..7a183a3 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -17,12 +17,17 @@
#define DEBUG false
#include "Log.h"
+#include "StatsPullerManager.h"
+
#include <android/os/IStatsCompanionService.h>
#include <android/os/IStatsPullerCallback.h>
#include <cutils/log.h>
#include <math.h>
#include <stdint.h>
+
#include <algorithm>
+#include <iostream>
+
#include "../StatsService.h"
#include "../logd/LogEvent.h"
#include "../stats_log_util.h"
@@ -32,13 +37,11 @@
#include "ResourceHealthManagerPuller.h"
#include "StatsCallbackPuller.h"
#include "StatsCompanionServicePuller.h"
-#include "StatsPullerManager.h"
#include "SubsystemSleepStatePuller.h"
+#include "SurfaceflingerStatsPuller.h"
#include "TrainInfoPuller.h"
#include "statslog.h"
-#include <iostream>
-
using std::make_shared;
using std::map;
using std::shared_ptr;
@@ -269,6 +272,10 @@
// App ops
{android::util::APP_OPS,
{.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}},
+ // SurfaceflingerStatsGlobalInfo
+ {android::util::SURFACEFLINGER_STATS_GLOBAL_INFO,
+ {.puller =
+ new SurfaceflingerStatsPuller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO)}},
};
StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp b/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp
new file mode 100644
index 0000000..23b2236
--- /dev/null
+++ b/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#include "SurfaceflingerStatsPuller.h"
+
+#include <cutils/compiler.h>
+
+#include <numeric>
+
+#include "logd/LogEvent.h"
+#include "stats_log_util.h"
+#include "statslog.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+SurfaceflingerStatsPuller::SurfaceflingerStatsPuller(const int tagId) : StatsPuller(tagId) {
+}
+
+bool SurfaceflingerStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) {
+ switch (mTagId) {
+ case android::util::SURFACEFLINGER_STATS_GLOBAL_INFO:
+ return pullGlobalInfo(data);
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static int64_t getTotalTime(
+ const google::protobuf::RepeatedPtrField<surfaceflinger::SFTimeStatsHistogramBucketProto>&
+ buckets) {
+ int64_t total = 0;
+ for (const auto& bucket : buckets) {
+ if (bucket.time_millis() == 1000) {
+ continue;
+ }
+
+ total += bucket.time_millis() * bucket.frame_count();
+ }
+
+ return total;
+}
+
+bool SurfaceflingerStatsPuller::pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data) {
+ std::string protoBytes;
+ if (CC_UNLIKELY(mStatsProvider)) {
+ protoBytes = mStatsProvider();
+ } else {
+ std::unique_ptr<FILE, decltype(&pclose)> pipe(popen("dumpsys SurfaceFlinger --timestats -dump --proto", "r"), pclose);
+ if (!pipe.get()) {
+ return false;
+ }
+ char buf[1024];
+ size_t bytesRead = 0;
+ do {
+ bytesRead = fread(buf, 1, sizeof(buf), pipe.get());
+ protoBytes.append(buf, bytesRead);
+ } while (bytesRead > 0);
+ }
+ surfaceflinger::SFTimeStatsGlobalProto proto;
+ proto.ParseFromString(protoBytes);
+
+ int64_t totalTime = getTotalTime(proto.present_to_present());
+
+ data->clear();
+ data->reserve(1);
+ std::shared_ptr<LogEvent> event =
+ make_shared<LogEvent>(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, getWallClockNs(),
+ getElapsedRealtimeNs());
+ if (!event->write(proto.total_frames())) return false;
+ if (!event->write(proto.missed_frames())) return false;
+ if (!event->write(proto.client_composition_frames())) return false;
+ if (!event->write(proto.display_on_time())) return false;
+ if (!event->write(totalTime)) return false;
+ event->init();
+ data->emplace_back(event);
+
+ return true;
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h b/cmds/statsd/src/external/SurfaceflingerStatsPuller.h
new file mode 100644
index 0000000..ed7153e
--- /dev/null
+++ b/cmds/statsd/src/external/SurfaceflingerStatsPuller.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#pragma once
+
+#include <timestatsproto/TimeStatsProtoHeader.h>
+
+#include "StatsPuller.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Pull metrics from Surfaceflinger
+ */
+class SurfaceflingerStatsPuller : public StatsPuller {
+public:
+ explicit SurfaceflingerStatsPuller(const int tagId);
+
+ // StatsPuller interface
+ bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override;
+
+protected:
+ // Test-only, for injecting fake data
+ using StatsProvider = std::function<std::string()>;
+ StatsProvider mStatsProvider;
+
+private:
+ bool pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data);
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index fe25a25..76ee9a6 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -1713,6 +1713,11 @@
EXPECT_EQ(kActive, activation1004->state);
EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1004->activationType);
// }}}------------------------------------------------------------------------------
+
+ // Clear the data stored on disk as a result of the system server death.
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey1, configAddedTimeNs + NS_PER_SEC, false, true,
+ ADB_DUMP, FAST, &buffer);
}
#else
diff --git a/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp b/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
index b98dc60..325e869 100644
--- a/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
@@ -96,6 +96,11 @@
EXPECT_EQ((int64_t)(bucketStartTimeNs + 25 * bucketSizeNs + 2 + 2 * 3600 * NS_PER_SEC),
processor->mMetricsManagers.begin()->second->getTtlEndNs());
+
+ // Clear the data stored on disk as a result of the ttl.
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 25 * bucketSizeNs + 3, false, true,
+ ADB_DUMP, FAST, &buffer);
}
diff --git a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
new file mode 100644
index 0000000..5c9636f
--- /dev/null
+++ b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "SurfaceflingerStatsPuller_test"
+
+#include "src/external/SurfaceflingerStatsPuller.h"
+
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class TestableSurfaceflingerStatsPuller : public SurfaceflingerStatsPuller {
+public:
+ TestableSurfaceflingerStatsPuller(const int tagId) : SurfaceflingerStatsPuller(tagId){};
+
+ void injectStats(const StatsProvider& statsProvider) {
+ mStatsProvider = statsProvider;
+ }
+};
+
+class SurfaceflingerStatsPullerTest : public ::testing::Test {
+public:
+ SurfaceflingerStatsPullerTest() {
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+ }
+
+ ~SurfaceflingerStatsPullerTest() {
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+ }
+};
+
+TEST_F(SurfaceflingerStatsPullerTest, pullGlobalStats) {
+ surfaceflinger::SFTimeStatsGlobalProto proto;
+ proto.set_total_frames(1);
+ proto.set_missed_frames(2);
+ proto.set_client_composition_frames(2);
+ proto.set_display_on_time(4);
+
+ auto bucketOne = proto.add_present_to_present();
+ bucketOne->set_time_millis(2);
+ bucketOne->set_frame_count(4);
+ auto bucketTwo = proto.add_present_to_present();
+ bucketTwo->set_time_millis(4);
+ bucketTwo->set_frame_count(1);
+ auto bucketThree = proto.add_present_to_present();
+ bucketThree->set_time_millis(1000);
+ bucketThree->set_frame_count(1);
+ static constexpr int64_t expectedAnimationMillis = 12;
+ TestableSurfaceflingerStatsPuller puller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO);
+
+ puller.injectStats([&] {
+ return proto.SerializeAsString();
+ });
+ puller.ForceClearCache();
+ vector<std::shared_ptr<LogEvent>> outData;
+ puller.Pull(&outData);
+
+ ASSERT_EQ(1, outData.size());
+ EXPECT_EQ(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, outData[0]->GetTagId());
+ EXPECT_EQ(proto.total_frames(), outData[0]->getValues()[0].mValue.long_value);
+ EXPECT_EQ(proto.missed_frames(), outData[0]->getValues()[1].mValue.long_value);
+ EXPECT_EQ(proto.client_composition_frames(), outData[0]->getValues()[2].mValue.long_value);
+ EXPECT_EQ(proto.display_on_time(), outData[0]->getValues()[3].mValue.long_value);
+ EXPECT_EQ(expectedAnimationMillis, outData[0]->getValues()[4].mValue.long_value);
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 3e4649f..f28b85c 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -308,6 +308,17 @@
public abstract String getNameForUid(int uid);
/**
+ * Marks a package as installed (or not installed) for a given user.
+ *
+ * @param pkg the package whose installation is to be set
+ * @param userId the user for whom to set it
+ * @param installed the new installed state
+ * @return true if the installed state changed as a result
+ */
+ public abstract boolean setInstalled(PackageParser.Package pkg,
+ @UserIdInt int userId, boolean installed);
+
+ /**
* Request to perform the second phase of ephemeral resolution.
* @param responseObj The response of the first phase of ephemeral resolution
* @param origIntent The original intent that triggered ephemeral resolution
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index e65d761..df652f1 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -126,6 +126,13 @@
public static final int FLAG_SYSTEM = 0x00000800;
/**
+ * Indicates that this user is some sort of profile. Right now, the only profile type is
+ * {@link #FLAG_MANAGED_PROFILE}, but this can include other types of profiles too if any
+ * are created in the future. This is therefore not a flag, but an OR of several flags.
+ */
+ public static final int PROFILE_FLAGS_MASK = FLAG_MANAGED_PROFILE;
+
+ /**
* @hide
*/
@IntDef(flag = true, prefix = "FLAG_", value = {
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 9049c3a..e415b41 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -33,15 +33,30 @@
* Reports that a compatibility change is affecting an app process now.
*
* <p>Note: for changes that are gated using {@link #isChangeEnabled(long, ApplicationInfo)},
- * you do not need to call this API directly. The change will be reported for you in the case
- * that {@link #isChangeEnabled(long, ApplicationInfo)} returns {@code true}.
+ * you do not need to call this API directly. The change will be reported for you.
*
* @param changeId The ID of the compatibility change taking effect.
- * @param appInfo Representing the affected app.
+ * @param appInfo Representing the affected app.
*/
void reportChange(long changeId, in ApplicationInfo appInfo);
/**
+ * Reports that a compatibility change is affecting an app process now.
+ *
+ * <p>Same as {@link #reportChange(long, ApplicationInfo)}, except it receives a package name
+ * instead of an {@link ApplicationInfo}
+ * object, and finds an app info object based on the package name. Returns {@code true} if
+ * there is no installed package by that name.
+ *
+ * <p>Note: for changes that are gated using {@link #isChangeEnabled(long, String)},
+ * you do not need to call this API directly. The change will be reported for you.
+ *
+ * @param changeId The ID of the compatibility change taking effect.
+ * @param packageName The package name of the app in question.
+ */
+ void reportChangeByPackageName(long changeId, in String packageName);
+
+ /**
* Query if a given compatibility change is enabled for an app process. This method should
* be called when implementing functionality on behalf of the affected app.
*
@@ -49,13 +64,35 @@
* change, resulting in differing behaviour compared to earlier releases. If this method returns
* {@code false}, the calling code should behave as it did in earlier releases.
*
- * <p>When this method returns {@code true}, it will also report the change as
- * {@link #reportChange(long, ApplicationInfo)} would, so there is no need to call that method
- * directly.
+ * <p>It will also report the change as {@link #reportChange(long, ApplicationInfo)} would, so
+ * there is no need to call that method directly.
*
* @param changeId The ID of the compatibility change in question.
- * @param appInfo Representing the app in question.
+ * @param appInfo Representing the app in question.
* @return {@code true} if the change is enabled for the current app.
*/
boolean isChangeEnabled(long changeId, in ApplicationInfo appInfo);
+
+ /**
+ * Query if a given compatibility change is enabled for an app process. This method should
+ * be called when implementing functionality on behalf of the affected app.
+ *
+ * <p>Same as {@link #isChangeEnabled(long, ApplicationInfo)}, except it receives a package name
+ * instead of an {@link ApplicationInfo}
+ * object, and finds an app info object based on the package name. Returns {@code true} if
+ * there is no installed package by that name.
+ *
+ * <p>If this method returns {@code true}, the calling code should implement the compatibility
+ * change, resulting in differing behaviour compared to earlier releases. If this method
+ * returns
+ * {@code false}, the calling code should behave as it did in earlier releases.
+ *
+ * <p>It will also report the change as {@link #reportChange(long, String)} would, so there is
+ * no need to call that method directly.
+ *
+ * @param changeId The ID of the compatibility change in question.
+ * @param packageName The package name of the app in question.
+ * @return {@code true} if the change is enabled for the current app.
+ */
+ boolean isChangeEnabledByPackageName(long changeId, in String packageName);
}
\ No newline at end of file
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 7cd3e95..697825d 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -35,6 +35,7 @@
import android.util.SparseArray;
import android.util.Xml;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import libcore.io.IoUtils;
@@ -50,6 +51,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Loads global system configuration info.
@@ -209,6 +211,10 @@
private final ArraySet<String> mBugreportWhitelistedPackages = new ArraySet<>();
+ // Map of packagesNames to userTypes. Stored temporarily until cleared by UserManagerService().
+ private ArrayMap<String, Set<String>> mPackageToUserTypeWhitelist = new ArrayMap<>();
+ private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>();
+
public static SystemConfig getInstance() {
if (!isSystemProcess()) {
Slog.wtf(TAG, "SystemConfig is being accessed by a process other than "
@@ -359,7 +365,48 @@
return mBugreportWhitelistedPackages;
}
+ /**
+ * Gets map of packagesNames to userTypes, dictating on which user types each package should be
+ * initially installed, and then removes this map from SystemConfig.
+ * Called by UserManagerService when it is constructed.
+ */
+ public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeWhitelist() {
+ ArrayMap<String, Set<String>> r = mPackageToUserTypeWhitelist;
+ mPackageToUserTypeWhitelist = new ArrayMap<>(0);
+ return r;
+ }
+
+ /**
+ * Gets map of packagesNames to userTypes, dictating on which user types each package should NOT
+ * be initially installed, even if they are whitelisted, and then removes this map from
+ * SystemConfig.
+ * Called by UserManagerService when it is constructed.
+ */
+ public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeBlacklist() {
+ ArrayMap<String, Set<String>> r = mPackageToUserTypeBlacklist;
+ mPackageToUserTypeBlacklist = new ArrayMap<>(0);
+ return r;
+ }
+
+ /**
+ * Only use for testing. Do NOT use in production code.
+ * @param readPermissions false to create an empty SystemConfig; true to read the permissions.
+ */
+ @VisibleForTesting
+ protected SystemConfig(boolean readPermissions) {
+ if (readPermissions) {
+ Slog.w(TAG, "Constructing a test SystemConfig");
+ readAllPermissions();
+ } else {
+ Slog.w(TAG, "Constructing an empty test SystemConfig");
+ }
+ }
+
SystemConfig() {
+ readAllPermissions();
+ }
+
+ private void readAllPermissions() {
// Read configuration from system
readPermissions(Environment.buildPath(
Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
@@ -419,7 +466,8 @@
Environment.getSystemExtDirectory(), "etc", "permissions"), ALLOW_ALL);
}
- void readPermissions(File libraryDir, int permissionFlag) {
+ @VisibleForTesting
+ public void readPermissions(File libraryDir, int permissionFlag) {
// Read permissions from given directory.
if (!libraryDir.exists() || !libraryDir.isDirectory()) {
if (permissionFlag == ALLOW_ALL) {
@@ -954,6 +1002,11 @@
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "install-in-user-type": {
+ // NB: We allow any directory permission to declare install-in-user-type.
+ readInstallInUserType(parser,
+ mPackageToUserTypeWhitelist, mPackageToUserTypeBlacklist);
+ } break;
default: {
Slog.w(TAG, "Tag " + name + " is unknown in "
+ permFile + " at " + parser.getPositionDescription());
@@ -1091,6 +1144,53 @@
}
}
+ private void readInstallInUserType(XmlPullParser parser,
+ Map<String, Set<String>> doInstallMap,
+ Map<String, Set<String>> nonInstallMap)
+ throws IOException, XmlPullParserException {
+ final String packageName = parser.getAttributeValue(null, "package");
+ if (TextUtils.isEmpty(packageName)) {
+ Slog.w(TAG, "package is required for <install-in-user-type> in "
+ + parser.getPositionDescription());
+ return;
+ }
+
+ Set<String> userTypesYes = doInstallMap.get(packageName);
+ Set<String> userTypesNo = nonInstallMap.get(packageName);
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ final String name = parser.getName();
+ if ("install-in".equals(name)) {
+ final String userType = parser.getAttributeValue(null, "user-type");
+ if (TextUtils.isEmpty(userType)) {
+ Slog.w(TAG, "user-type is required for <install-in-user-type> in "
+ + parser.getPositionDescription());
+ continue;
+ }
+ if (userTypesYes == null) {
+ userTypesYes = new ArraySet<>();
+ doInstallMap.put(packageName, userTypesYes);
+ }
+ userTypesYes.add(userType);
+ } else if ("do-not-install-in".equals(name)) {
+ final String userType = parser.getAttributeValue(null, "user-type");
+ if (TextUtils.isEmpty(userType)) {
+ Slog.w(TAG, "user-type is required for <install-in-user-type> in "
+ + parser.getPositionDescription());
+ continue;
+ }
+ if (userTypesNo == null) {
+ userTypesNo = new ArraySet<>();
+ nonInstallMap.put(packageName, userTypesNo);
+ }
+ userTypesNo.add(userType);
+ } else {
+ Slog.w(TAG, "unrecognized tag in <install-in-user-type> in "
+ + parser.getPositionDescription());
+ }
+ }
+ }
+
void readOemPermissions(XmlPullParser parser) throws IOException, XmlPullParserException {
final String packageName = parser.getAttributeValue(null, "package");
if (TextUtils.isEmpty(packageName)) {
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b4374fe..d46fe8d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -696,26 +696,32 @@
// Read if we are using the profile configuration, do this at the start since the last ART args
// take precedence.
property_get("dalvik.vm.profilebootclasspath", propBuf, "");
- std::string profile_boot_class_path = propBuf;
+ std::string profile_boot_class_path_flag = propBuf;
// Empty means the property is unset and we should default to the phenotype property.
// The possible values are {"true", "false", ""}
- if (profile_boot_class_path.empty()) {
- profile_boot_class_path = server_configurable_flags::GetServerConfigurableFlag(
+ if (profile_boot_class_path_flag.empty()) {
+ profile_boot_class_path_flag = server_configurable_flags::GetServerConfigurableFlag(
RUNTIME_NATIVE_BOOT_NAMESPACE,
PROFILE_BOOT_CLASS_PATH,
/*default_value=*/ "");
}
- if (profile_boot_class_path == "true") {
+ const bool profile_boot_class_path = (profile_boot_class_path_flag == "true");
+ if (profile_boot_class_path) {
+ addOption("-Xcompiler-option");
+ addOption("--count-hotness-in-compiled-code");
addOption("-Xps-profile-boot-class-path");
addOption("-Xps-profile-aot-code");
addOption("-Xjitsaveprofilinginfo");
}
- std::string use_apex_image =
+ std::string use_apex_image_flag =
server_configurable_flags::GetServerConfigurableFlag(RUNTIME_NATIVE_BOOT_NAMESPACE,
ENABLE_APEX_IMAGE,
/*default_value=*/ "");
- if (use_apex_image == "true") {
+ // Use the APEX boot image for boot class path profiling to get JIT samples on BCP methods.
+ // Also use the APEX boot image if it's explicitly enabled via configuration flag.
+ const bool use_apex_image = profile_boot_class_path || (use_apex_image_flag == "true");
+ if (use_apex_image) {
addOption(kApexImageOption);
ALOGI("Using Apex boot image: '%s'\n", kApexImageOption);
} else if (parseRuntimeOption("dalvik.vm.boot-image", bootImageBuf, "-Ximage:")) {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5fd53de..e3337b7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2527,6 +2527,16 @@
will be locked. -->
<bool name="config_multiuserDelayUserDataLocking">false</bool>
+ <!-- Whether to only install system packages on a user if they're whitelisted for that user
+ type. These are flags and can be freely combined.
+ 0 (0b000) - disable whitelist (install all system packages; no logging)
+ 1 (0b001) - enforce (only install system packages if they are whitelisted)
+ 2 (0b010) - log (log when a non-whitelisted package is run)
+ 4 (0b100) - treat any package not mentioned in the whitelist file as implicitly whitelisted
+ Note: This list must be kept current with PACKAGE_WHITELIST_MODE_PROP in
+ frameworks/base/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java -->
+ <integer name="config_userTypePackageWhitelistMode">5</integer> <!-- 0b101 -->
+
<!-- Whether UI for multi user should be shown -->
<bool name="config_enableMultiUserUI">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 761e02f..3fe907c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -511,6 +511,7 @@
<java-symbol type="integer" name="config_multiuserMaximumUsers" />
<java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
<java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
+ <java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
<java-symbol type="integer" name="config_safe_media_volume_index" />
<java-symbol type="integer" name="config_safe_media_volume_usb_mB" />
<java-symbol type="integer" name="config_mobile_mtu" />
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 7e167c9..befa637 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -22,6 +22,12 @@
}
prebuilt_etc {
+ name: "preinstalled-packages-platform.xml",
+ sub_dir: "sysconfig",
+ src: "preinstalled-packages-platform.xml",
+}
+
+prebuilt_etc {
name: "hiddenapi-package-whitelist.xml",
sub_dir: "sysconfig",
src: "hiddenapi-package-whitelist.xml",
diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml
new file mode 100644
index 0000000..ccd8b5b
--- /dev/null
+++ b/data/etc/preinstalled-packages-platform.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<!--
+This XML file declares which system packages should be initially installed for new users based on
+the type of user. All system packages on the device should ideally have an entry in an xml file
+(keys by its manifest name).
+
+Main user-types (every user will be at least one of these types) are:
+ SYSTEM (user 0)
+ FULL (any non-profile human user)
+ PROFILE (profile human user)
+
+Additional optional types are: GUEST, RESTRICTED, MANAGED_PROFILE, EPHEMERAL, DEMO
+
+The meaning of each of these user types is delineated by flags in
+frameworks/base/core/java/android/content/pm/UserInfo.java.
+See frameworks/base/services/core/java/com/android/server/pm/UserSystemPackageInstaller#getFlagsFromUserTypes
+
+The following three examples should cover most normal cases:
+
+1. For a system package to be pre-installed only in user 0:
+
+ <install-in-user-type package="com.android.example">
+ <install-in user-type="SYSTEM">
+ </install-in-user-type>
+
+
+2. For a system package to be pre-installed on all human users (e.g. a web browser), i.e. to be
+installed on any user of type type FULL or PROFILE (since this covers all human users):
+
+ <install-in-user-type package="com.android.example">
+ <install-in user-type="FULL">
+ <install-in user-type="PROFILE">
+ </install-in-user-type>
+
+
+3. For a system package to be pre-installed on all human users except for profile users (e.g. a
+wallpaper app, since profiles cannot display wallpaper):
+
+ <install-in-user-type package="com.android.example">
+ <install-in user-type="FULL">
+ </install-in-user-type>
+
+
+Some system packages truly are required to be on all users, regardless of type, in which case use:
+ <install-in-user-type package="com.android.example">
+ <install-in user-type="SYSTEM">
+ <install-in user-type="FULL">
+ <install-in user-type="PROFILE">
+ </install-in-user-type>
+
+More fine-grained options are also available (see below). Additionally, packages can blacklist
+user types. Blacklists override any whitelisting (in any file).
+E.g.
+ <install-in-user-type package="com.android.example">
+ <install-in user-type="FULL" />
+ <do-not-install-in user-type="GUEST" />
+ </install-in-user-type>
+
+If a user is of type FULL and GUEST, this package will NOT be installed, because the
+'do-not-install-in' takes precedence over 'install-in'.
+
+The way that a device treats system packages that do not have any entry (for any user type) at all
+is determined by the config resource value config_userTypePackageWhitelistMode.
+See frameworks/base/core/res/res/values/config.xml#config_userTypePackageWhitelistMode.
+
+Changes to the whitelist during system updates can result in installing new system packages
+to pre-existing users, but cannot uninstall system packages from pre-existing users.
+-->
+<config>
+ <install-in-user-type package="com.android.providers.settings">
+ <install-in user-type="SYSTEM" />
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+</config>
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 72ada6e..8240345 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -126,8 +126,6 @@
static final int DISMISS_GROUP_CANCELLED = 9;
static final int DISMISS_INVALID_INTENT = 10;
- public static final int MAX_BUBBLES = 5; // TODO: actually enforce this
-
private final Context mContext;
private final NotificationEntryManager mNotificationEntryManager;
private final BubbleTaskStackListener mTaskStackListener;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 31cf853..340dced 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -181,7 +181,9 @@
*/
private float mVerticalPosPercentBeforeRotation = -1;
+ private int mMaxBubbles;
private int mBubbleSize;
+ private int mBubbleElevation;
private int mBubblePaddingTop;
private int mBubbleTouchPadding;
private int mExpandedViewPadding;
@@ -326,7 +328,9 @@
mInflater = LayoutInflater.from(context);
Resources res = getResources();
+ mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+ mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
mExpandedAnimateXDistance =
@@ -1597,8 +1601,7 @@
for (int i = 0; i < bubbleCount; i++) {
BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
bv.updateDotVisibility(true /* animate */);
- bv.setZ((BubbleController.MAX_BUBBLES
- * getResources().getDimensionPixelSize(R.dimen.bubble_elevation)) - i);
+ bv.setZ((mMaxBubbles * mBubbleElevation) - i);
// If the dot is on the left, and so is the stack, we need to change the dot position.
if (bv.getDotPositionOnLeft() == mStackOnLeftOrWillBe) {
bv.setDotPosition(!mStackOnLeftOrWillBe, animate);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 1400921..6c0f90a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -21,6 +21,7 @@
import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_CHILD_NOTIFICATIONS;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
import android.content.ComponentName;
@@ -57,8 +58,9 @@
private final NotificationGroupManager mGroupManager =
Dependency.get(NotificationGroupManager.class);
- private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
private final Context mContext;
+ private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
+ @Nullable private NotifServiceListener mDownstreamListener;
@Inject
public NotificationListener(Context context) {
@@ -69,6 +71,10 @@
mSettingsListeners.add(listener);
}
+ public void setDownstreamListener(NotifServiceListener downstreamListener) {
+ mDownstreamListener = downstreamListener;
+ }
+
@Override
public void onListenerConnected() {
if (DEBUG) Log.d(TAG, "onListenerConnected");
@@ -81,6 +87,9 @@
final RankingMap currentRanking = getCurrentRanking();
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
for (StatusBarNotification sbn : notifications) {
+ if (mDownstreamListener != null) {
+ mDownstreamListener.onNotificationPosted(sbn, currentRanking);
+ }
mEntryManager.addNotification(sbn, currentRanking);
}
});
@@ -95,6 +104,11 @@
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
processForRemoteInput(sbn.getNotification(), mContext);
+
+ if (mDownstreamListener != null) {
+ mDownstreamListener.onNotificationPosted(sbn, rankingMap);
+ }
+
String key = sbn.getKey();
boolean isUpdate =
mEntryManager.getNotificationData().get(key) != null;
@@ -133,6 +147,9 @@
if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
final String key = sbn.getKey();
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
+ if (mDownstreamListener != null) {
+ mDownstreamListener.onNotificationRemoved(sbn, rankingMap, reason);
+ }
mEntryManager.removeNotification(key, rankingMap, reason);
});
}
@@ -149,6 +166,9 @@
if (rankingMap != null) {
RankingMap r = onPluginRankingUpdate(rankingMap);
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
+ if (mDownstreamListener != null) {
+ mDownstreamListener.onNotificationRankingUpdate(rankingMap);
+ }
mEntryManager.updateNotificationRanking(r);
});
}
@@ -175,4 +195,12 @@
default void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) { }
}
+
+ /** Interface for listening to add/remove events that we receive from NotificationManager. */
+ public interface NotifServiceListener {
+ void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap);
+ void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap);
+ void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, int reason);
+ void onNotificationRankingUpdate(RankingMap rankingMap);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java
new file mode 100644
index 0000000..df70828
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 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.systemui.statusbar.notification;
+
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import com.android.systemui.statusbar.NotificationListener;
+
+import javax.inject.Inject;
+
+/**
+ * Initialization code for the new notification pipeline.
+ */
+public class NotifPipelineInitializer {
+
+ @Inject
+ public NotifPipelineInitializer() {
+ }
+
+ public void initialize(
+ NotificationListener notificationService) {
+
+ // TODO Put real code here
+ notificationService.setDownstreamListener(new NotificationListener.NotifServiceListener() {
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn,
+ NotificationListenerService.RankingMap rankingMap) {
+ Log.d(TAG, "onNotificationPosted " + sbn.getKey());
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn,
+ NotificationListenerService.RankingMap rankingMap) {
+ Log.d(TAG, "onNotificationRemoved " + sbn.getKey());
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn,
+ NotificationListenerService.RankingMap rankingMap, int reason) {
+ Log.d(TAG, "onNotificationRemoved " + sbn.getKey());
+ }
+
+ @Override
+ public void onNotificationRankingUpdate(
+ NotificationListenerService.RankingMap rankingMap) {
+ Log.d(TAG, "onNotificationRankingUpdate");
+ }
+ });
+ }
+
+ private static final String TAG = "NotifInitializer";
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index f06fbbd..377d138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -197,6 +197,7 @@
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotifPipelineInitializer;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
import com.android.systemui.statusbar.notification.NotificationClicker;
@@ -390,6 +391,8 @@
@Inject
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
boolean mAllowNotificationLongPress;
+ @Inject
+ protected NotifPipelineInitializer mNotifPipelineInitializer;
// expanded notifications
protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
@@ -1128,6 +1131,8 @@
mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
mNotificationListController.bind();
+
+ mNotifPipelineInitializer.initialize(mNotificationListener);
}
/**
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c07f67b..d7a68a1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15267,7 +15267,7 @@
mBatteryStatsService.removeUid(uid);
if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
mAppOpsService.resetAllModes(UserHandle.getUserId(uid),
- intent.getData().getSchemeSpecificPart());
+ intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
} else {
mAppOpsService.uidRemoved(uid);
}
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 81e507c..33d8dec 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.util.Slog;
import android.util.StatsLog;
@@ -49,6 +50,15 @@
}
@Override
+ public void reportChangeByPackageName(long changeId, String packageName) {
+ ApplicationInfo appInfo = getApplicationInfo(packageName);
+ if (appInfo == null) {
+ return;
+ }
+ reportChange(changeId, appInfo);
+ }
+
+ @Override
public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
if (CompatConfig.get().isChangeEnabled(changeId, appInfo)) {
reportChange(changeId, appInfo,
@@ -61,11 +71,29 @@
}
@Override
+ public boolean isChangeEnabledByPackageName(long changeId, String packageName) {
+ ApplicationInfo appInfo = getApplicationInfo(packageName);
+ if (appInfo == null) {
+ return true;
+ }
+ return isChangeEnabled(changeId, appInfo);
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
CompatConfig.get().dumpConfig(pw);
}
+ private ApplicationInfo getApplicationInfo(String packageName) {
+ try {
+ return mContext.getPackageManager().getApplicationInfo(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "No installed package " + packageName);
+ }
+ return null;
+ }
+
private void reportChange(long changeId, ApplicationInfo appInfo, int state) {
int uid = appInfo.uid;
//TODO(b/138374585): Implement rate limiting for the logs.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 123e65e..d07e2d2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -137,7 +137,6 @@
import android.content.IntentSender.SendIntentException;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.pm.AppsQueryHelper;
import android.content.pm.AuxiliaryResolveInfo;
import android.content.pm.ChangedPackages;
import android.content.pm.ComponentInfo;
@@ -2473,54 +2472,21 @@
PackageManagerService m = new PackageManagerService(injector, factoryTest, onlyCore);
t.traceEnd(); // "create package manager"
- m.enableSystemUserPackages();
+ m.installWhitelistedSystemPackages();
ServiceManager.addService("package", m);
final PackageManagerNative pmn = m.new PackageManagerNative();
ServiceManager.addService("package_native", pmn);
return m;
}
- private void enableSystemUserPackages() {
- if (!UserManager.isSplitSystemUser()) {
- return;
- }
- // For system user, enable apps based on the following conditions:
- // - app is whitelisted or belong to one of these groups:
- // -- system app which has no launcher icons
- // -- system app which has INTERACT_ACROSS_USERS permission
- // -- system IME app
- // - app is not in the blacklist
- AppsQueryHelper queryHelper = new AppsQueryHelper(this);
- Set<String> enableApps = new ArraySet<>();
- enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_NON_LAUNCHABLE_APPS
- | AppsQueryHelper.GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM
- | AppsQueryHelper.GET_IMES, /* systemAppsOnly */ true, UserHandle.SYSTEM));
- ArraySet<String> wlApps = SystemConfig.getInstance().getSystemUserWhitelistedApps();
- enableApps.addAll(wlApps);
- enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_REQUIRED_FOR_SYSTEM_USER,
- /* systemAppsOnly */ false, UserHandle.SYSTEM));
- ArraySet<String> blApps = SystemConfig.getInstance().getSystemUserBlacklistedApps();
- enableApps.removeAll(blApps);
- Log.i(TAG, "Applications installed for system user: " + enableApps);
- List<String> allAps = queryHelper.queryApps(0, /* systemAppsOnly */ false,
- UserHandle.SYSTEM);
- final int allAppsSize = allAps.size();
+ /** Install/uninstall system packages for all users based on their user-type, as applicable. */
+ private void installWhitelistedSystemPackages() {
synchronized (mLock) {
- for (int i = 0; i < allAppsSize; i++) {
- String pName = allAps.get(i);
- PackageSetting pkgSetting = mSettings.mPackages.get(pName);
- // Should not happen, but we shouldn't be failing if it does
- if (pkgSetting == null) {
- continue;
- }
- boolean install = enableApps.contains(pName);
- if (pkgSetting.getInstalled(UserHandle.USER_SYSTEM) != install) {
- Log.i(TAG, (install ? "Installing " : "Uninstalling ") + pName
- + " for system user");
- pkgSetting.setInstalled(install, UserHandle.USER_SYSTEM);
- }
+ final boolean scheduleWrite = mUserManager.installWhitelistedSystemPackages(
+ isFirstBoot(), isDeviceUpgrading());
+ if (scheduleWrite) {
+ scheduleWritePackageRestrictionsLocked(UserHandle.USER_ALL);
}
- scheduleWritePackageRestrictionsLocked(UserHandle.USER_SYSTEM);
}
}
@@ -17746,8 +17712,14 @@
}
}
if (removedAppId >= 0) {
+ // If a system app's updates are uninstalled the UID is not actually removed. Some
+ // services need to know the package name affected.
+ if (extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
+ extras.putString(Intent.EXTRA_PACKAGE_NAME, removedPackage);
+ }
+
packageSender.sendPackageBroadcast(Intent.ACTION_UID_REMOVED,
- removedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
+ null, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
null, null, broadcastUsers, instantUserIds);
}
}
@@ -22312,10 +22284,19 @@
}
}
- /** Called by UserManagerService */
- void createNewUser(int userId, String[] disallowedPackages) {
+ /**
+ * Called by UserManagerService.
+ *
+ * @param installablePackages system packages that should be initially installed for this user,
+ * or {@code null} if all system packages should be installed
+ * @param disallowedPackages packages that should not be initially installed. Takes precedence
+ * over installablePackages.
+ */
+ void createNewUser(int userId, @Nullable Set<String> installablePackages,
+ String[] disallowedPackages) {
synchronized (mInstallLock) {
- mSettings.createNewUserLI(this, mInstaller, userId, disallowedPackages);
+ mSettings.createNewUserLI(this, mInstaller, userId,
+ installablePackages, disallowedPackages);
}
synchronized (mLock) {
scheduleWritePackageRestrictionsLocked(userId);
@@ -23124,6 +23105,19 @@
}
@Override
+ public boolean setInstalled(PackageParser.Package pkg, @UserIdInt int userId,
+ boolean installed) {
+ synchronized (mLock) {
+ final PackageSetting ps = mSettings.mPackages.get(pkg.packageName);
+ if (ps.getInstalled(userId) != installed) {
+ ps.setInstalled(installed, userId);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ @Override
public void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
Intent origIntent, String resolvedType, String callingPackage,
Bundle verificationBundle, int userId) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 1873a4e..4349ea7 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4014,8 +4014,9 @@
}
}
- void createNewUserLI(@NonNull PackageManagerService service,
- @NonNull Installer installer, int userHandle, String[] disallowedPackages) {
+ void createNewUserLI(@NonNull PackageManagerService service, @NonNull Installer installer,
+ @UserIdInt int userHandle, @Nullable Set<String> installablePackages,
+ String[] disallowedPackages) {
final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
Trace.TRACE_TAG_PACKAGE_MANAGER);
t.traceBegin("createNewUser-" + userHandle);
@@ -4025,6 +4026,7 @@
String[] seinfos;
int[] targetSdkVersions;
int packagesCount;
+ final boolean skipPackageWhitelist = installablePackages == null;
synchronized (mLock) {
Collection<PackageSetting> packages = mPackages.values();
packagesCount = packages.size();
@@ -4040,6 +4042,7 @@
continue;
}
final boolean shouldInstall = ps.isSystem() &&
+ (skipPackageWhitelist || installablePackages.contains(ps.name)) &&
!ArrayUtils.contains(disallowedPackages, ps.name) &&
!ps.pkg.applicationInfo.hiddenUntilInstalled;
// Only system apps are initially installed.
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 9371c44..5f86708 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -250,6 +250,9 @@
private static final IBinder mUserRestriconToken = new Binder();
+ /** Installs system packages based on user-type. */
+ private final UserSystemPackageInstaller mSystemPackageInstaller;
+
/**
* Internal non-parcelable wrapper for UserInfo that is not exposed to other system apps.
*/
@@ -550,6 +553,7 @@
readUserListLP();
sInstance = this;
}
+ mSystemPackageInstaller = new UserSystemPackageInstaller(this);
mLocalService = new LocalService();
LocalServices.addService(UserManagerInternal.class, mLocalService);
mLockPatternUtils = new LockPatternUtils(mContext);
@@ -2842,8 +2846,10 @@
StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
t.traceEnd();
+ final Set<String> installablePackages =
+ mSystemPackageInstaller.getInstallablePackagesForUserType(flags);
t.traceBegin("PM.createNewUser");
- mPm.createNewUser(userId, disallowedPackages);
+ mPm.createNewUser(userId, installablePackages, disallowedPackages);
t.traceEnd();
userInfo.partial = false;
@@ -2877,6 +2883,11 @@
return userInfo;
}
+ /** Install/uninstall system packages for all users based on their user-type, as applicable. */
+ boolean installWhitelistedSystemPackages(boolean isFirstBoot, boolean isUpgrade) {
+ return mSystemPackageInstaller.installWhitelistedSystemPackages(isFirstBoot, isUpgrade);
+ }
+
@VisibleForTesting
UserData putUserInfo(UserInfo userInfo) {
final UserData userData = new UserData();
@@ -3863,6 +3874,10 @@
pw.println(" Is split-system user: " + UserManager.isSplitSystemUser());
pw.println(" Is headless-system mode: " + UserManager.isHeadlessSystemUserMode());
pw.println(" User version: " + mUserVersion);
+
+ // Dump package whitelist
+ pw.println();
+ mSystemPackageInstaller.dump(pw);
}
private static void dumpTimeAgo(PrintWriter pw, StringBuilder sb, long nowTime, long time) {
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
new file mode 100644
index 0000000..036d1e8
--- /dev/null
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2019 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.server.pm;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageParser;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+
+/**
+ * Responsible for un/installing system packages based on user type.
+ *
+ * <p>Uses the SystemConfig's install-in-user-type whitelist;
+ * see {@link SystemConfig#getAndClearPackageToUserTypeWhitelist} and
+ * {@link SystemConfig#getAndClearPackageToUserTypeBlacklist}.
+ *
+ * <p>If {@link #isEnforceMode()} is false, then all system packages are always installed for all
+ * users. The following applies when it is true.
+ *
+ * Any package can be in one of three states in the SystemConfig whitelist
+ * <ol>
+ * <li>Explicitly blacklisted for a particular user type</li>
+ * <li>Explicitly whitelisted for a particular user type</li>
+ * <li>Not mentioned at all, for any user type (neither whitelisted nor blacklisted)</li>
+ * </ol>
+ * Blacklisting always takes precedence - if a package is blacklisted for a particular user,
+ * it won't be installed on that type of user (even if it is also whitelisted for that user).
+ * Next comes whitelisting - if it is whitelisted for a particular user, it will be installed on
+ * that type of user (as long as it isn't blacklisted).
+ * Finally, if the package is not mentioned at all (i.e. neither whitelisted nor blacklisted for
+ * any user types) in the SystemConfig 'install-in-user-type' lists
+ * then:
+ * <ul>
+ * <li>If {@link #isImplicitWhitelistMode()}, the package is implicitly treated as whitelisted
+ * for all users</li>
+ * <li>Otherwise, the package is implicitly treated as blacklisted for all non-SYSTEM users</li>
+ * <li>Either way, for {@link UserHandle#USER_SYSTEM}, the package will be implicitly
+ * whitelisted so that it can be used for local development purposes.</li>
+ * </ul>
+ */
+class UserSystemPackageInstaller {
+ private static final String TAG = "UserManagerService";
+
+ /**
+ * System Property whether to only install system packages on a user if they're whitelisted for
+ * that user type. These are flags and can be freely combined.
+ * <ul>
+ * <li> 0 (0b000) - disable whitelist (install all system packages; no logging)</li>
+ * <li> 1 (0b001) - enforce (only install system packages if they are whitelisted)</li>
+ * <li> 2 (0b010) - log (log when a non-whitelisted package is run)</li>
+ * <li> 4 (0b100) - implicitly whitelist any package not mentioned in the whitelist</li>
+ * <li>-1 - use device default (as defined in res/res/values/config.xml)</li>
+ * </ul>
+ * Note: This list must be kept current with config_userTypePackageWhitelistMode in
+ * frameworks/base/core/res/res/values/config.xml
+ */
+ static final String PACKAGE_WHITELIST_MODE_PROP = "persist.debug.user.package_whitelist_mode";
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0;
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0b001;
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_LOG = 0b010;
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0b100;
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT = -1;
+
+ @IntDef(flag = true, prefix = "USER_TYPE_PACKAGE_WHITELIST_MODE_", value = {
+ USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE,
+ USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE,
+ USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE,
+ USER_TYPE_PACKAGE_WHITELIST_MODE_LOG,
+ USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PackageWhitelistMode {}
+
+ /**
+ * Maps system package manifest names to the user flags on which they should be initially
+ * installed.
+ * <p>Packages that are whitelisted, but then blacklisted so that they aren't to be installed on
+ * any user, are purposefully still present in this list.
+ */
+ private final ArrayMap<String, Integer> mWhitelitsedPackagesForUserTypes;
+
+ private final UserManagerService mUm;
+
+ UserSystemPackageInstaller(UserManagerService ums) {
+ mUm = ums;
+ mWhitelitsedPackagesForUserTypes =
+ determineWhitelistedPackagesForUserTypes(SystemConfig.getInstance());
+ }
+
+ /** Constructor for testing purposes. */
+ @VisibleForTesting
+ UserSystemPackageInstaller(UserManagerService ums, ArrayMap<String, Integer> whitelist) {
+ mUm = ums;
+ mWhitelitsedPackagesForUserTypes = whitelist;
+ }
+
+ /**
+ * During OTAs and first boot, install/uninstall all system packages for all users based on the
+ * user's UserInfo flags and the SystemConfig whitelist.
+ * We do NOT uninstall packages during an OTA though.
+ *
+ * This is responsible for enforcing the whitelist for pre-existing users (i.e. USER_SYSTEM);
+ * enforcement for new users is done when they are created in UserManagerService.createUser().
+ */
+ boolean installWhitelistedSystemPackages(boolean isFirstBoot, boolean isUpgrade) {
+ final int mode = getWhitelistMode();
+ checkWhitelistedSystemPackages(mode);
+ if (!isUpgrade && !isFirstBoot) {
+ return false;
+ }
+ Slog.i(TAG, "Reviewing whitelisted packages due to "
+ + (isFirstBoot ? "[firstBoot]" : "") + (isUpgrade ? "[upgrade]" : ""));
+ final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+ // Install/uninstall system packages per user.
+ for (int userId : mUm.getUserIds()) {
+ final Set<String> userWhitelist = getInstallablePackagesForUserId(userId);
+ pmInt.forEachPackage(pkg -> {
+ if (!pkg.isSystem()) {
+ return;
+ }
+ final boolean install =
+ (userWhitelist == null || userWhitelist.contains(pkg.packageName))
+ && !pkg.applicationInfo.hiddenUntilInstalled;
+ if (isUpgrade && !isFirstBoot && !install) {
+ return; // To be careful, we don’t uninstall apps during OTAs
+ }
+ final boolean changed = pmInt.setInstalled(pkg, userId, install);
+ if (changed) {
+ Slog.i(TAG, (install ? "Installed " : "Uninstalled ")
+ + pkg.packageName + " for user " + userId);
+ }
+ });
+ }
+ return true;
+ }
+
+ /**
+ * Checks whether the system packages and the mWhitelistedPackagesForUserTypes whitelist are
+ * in 1-to-1 correspondence.
+ */
+ private void checkWhitelistedSystemPackages(@PackageWhitelistMode int mode) {
+ if (!isLogMode(mode) && !isEnforceMode(mode)) {
+ return;
+ }
+ Slog.v(TAG, "Checking that all system packages are whitelisted.");
+ final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages();
+ PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+
+ // Check whether all whitelisted packages are indeed on the system.
+ for (String pkgName : allWhitelistedPackages) {
+ PackageParser.Package pkg = pmInt.getPackage(pkgName);
+ if (pkg == null) {
+ Slog.w(TAG, pkgName + " is whitelisted but not present.");
+ } else if (!pkg.isSystem()) {
+ Slog.w(TAG, pkgName + " is whitelisted and present but not a system package.");
+ }
+ }
+
+ // Check whether all system packages are indeed whitelisted.
+ if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) {
+ return;
+ }
+ final boolean doWtf = isEnforceMode(mode);
+ pmInt.forEachPackage(pkg -> {
+ if (pkg.isSystem() && !allWhitelistedPackages.contains(pkg.manifestPackageName)) {
+ final String msg = "System package " + pkg.manifestPackageName
+ + " is not whitelisted using 'install-in-user-type' in SystemConfig "
+ + "for any user types!";
+ if (doWtf) {
+ Slog.wtf(TAG, msg);
+ } else {
+ Slog.e(TAG, msg);
+ }
+ }
+ });
+ }
+
+ /** Whether to only install system packages in new users for which they are whitelisted. */
+ boolean isEnforceMode() {
+ return isEnforceMode(getWhitelistMode());
+ }
+
+ /**
+ * Whether to log a warning concerning potential problems with the user-type package whitelist.
+ */
+ boolean isLogMode() {
+ return isLogMode(getWhitelistMode());
+ }
+
+ /**
+ * Whether to treat all packages that are not mentioned at all in the whitelist to be implicitly
+ * whitelisted for all users.
+ */
+ boolean isImplicitWhitelistMode() {
+ return isImplicitWhitelistMode(getWhitelistMode());
+ }
+
+ /** See {@link #isEnforceMode()}. */
+ private static boolean isEnforceMode(int whitelistMode) {
+ return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE) != 0;
+ }
+
+ /** See {@link #isLogMode()}. */
+ private static boolean isLogMode(int whitelistMode) {
+ return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_LOG) != 0;
+ }
+
+ /** See {@link #isImplicitWhitelistMode()}. */
+ private static boolean isImplicitWhitelistMode(int whitelistMode) {
+ return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST) != 0;
+ }
+
+ /** Gets the PackageWhitelistMode for use of {@link #mWhitelitsedPackagesForUserTypes}. */
+ private @PackageWhitelistMode int getWhitelistMode() {
+ final int runtimeMode = SystemProperties.getInt(
+ PACKAGE_WHITELIST_MODE_PROP, USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT);
+ if (runtimeMode != USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT) {
+ return runtimeMode;
+ }
+ return Resources.getSystem()
+ .getInteger(com.android.internal.R.integer.config_userTypePackageWhitelistMode);
+ }
+
+ /**
+ * Gets the system packages names that should be installed on the given user.
+ * See {@link #getInstallablePackagesForUserType(int)}.
+ */
+ private @Nullable Set<String> getInstallablePackagesForUserId(@UserIdInt int userId) {
+ return getInstallablePackagesForUserType(mUm.getUserInfo(userId).flags);
+ }
+
+ /**
+ * Gets the system package names that should be installed on a user with the given flags, as
+ * determined by SystemConfig, the whitelist mode, and the apps actually on the device.
+ * Names are the {@link PackageParser.Package#packageName}, not necessarily the manifest names.
+ *
+ * Returns null if all system packages should be installed (due enforce-mode being off).
+ */
+ @Nullable Set<String> getInstallablePackagesForUserType(int flags) {
+ final int mode = getWhitelistMode();
+ if (!isEnforceMode(mode)) {
+ return null;
+ }
+ final boolean isSystemUser = (flags & UserInfo.FLAG_SYSTEM) != 0;
+ final boolean isImplicitWhitelistMode = isImplicitWhitelistMode(mode);
+ final Set<String> whitelistedPackages = getWhitelistedPackagesForUserType(flags);
+
+ final Set<String> installPackages = new ArraySet<>();
+ final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+ pmInt.forEachPackage(pkg -> {
+ if (!pkg.isSystem()) {
+ return;
+ }
+ if (shouldInstallPackage(pkg, mWhitelitsedPackagesForUserTypes,
+ whitelistedPackages, isImplicitWhitelistMode, isSystemUser)) {
+ // Although the whitelist uses manifest names, this function returns packageNames.
+ installPackages.add(pkg.packageName);
+ }
+ });
+ return installPackages;
+ }
+
+ /**
+ * Returns whether the given system package should be installed on the given user, based on the
+ * the given whitelist of system packages.
+ *
+ * @param sysPkg the system package. Must be a system package; no verification for this is done.
+ * @param userTypeWhitelist map of package manifest names to user flags on which they should be
+ * installed
+ * @param userWhitelist set of package manifest names that should be installed on this
+ * particular user. This must be consistent with userTypeWhitelist, but is
+ * passed in separately to avoid repeatedly calculating it from
+ * userTypeWhitelist.
+ * @param isImplicitWhitelistMode whether non-mentioned packages are implicitly whitelisted.
+ * @param isSystemUser whether the user is USER_SYSTEM (which gets special treatment).
+ */
+ @VisibleForTesting
+ static boolean shouldInstallPackage(PackageParser.Package sysPkg,
+ @NonNull ArrayMap<String, Integer> userTypeWhitelist,
+ @NonNull Set<String> userWhitelist, boolean isImplicitWhitelistMode,
+ boolean isSystemUser) {
+
+ final String pkgName = sysPkg.manifestPackageName;
+ boolean install = (isImplicitWhitelistMode && !userTypeWhitelist.containsKey(pkgName))
+ || userWhitelist.contains(pkgName);
+
+ // For the purposes of local development, any package that isn't even mentioned in the
+ // whitelist at all is implicitly treated as whitelisted for the SYSTEM user.
+ if (!install && isSystemUser && !userTypeWhitelist.containsKey(pkgName)) {
+ install = true;
+ Slog.e(TAG, "System package " + pkgName + " is not mentioned "
+ + "in SystemConfig's 'install-in-user-type' but we are "
+ + "implicitly treating it as whitelisted for the SYSTEM user.");
+ }
+ return install;
+ }
+
+ /**
+ * Gets the package manifest names that are whitelisted for a user with the given flags,
+ * as determined by SystemConfig.
+ */
+ @VisibleForTesting
+ @NonNull Set<String> getWhitelistedPackagesForUserType(int flags) {
+ Set<String> installablePkgs = new ArraySet<>(mWhitelitsedPackagesForUserTypes.size());
+ for (int i = 0; i < mWhitelitsedPackagesForUserTypes.size(); i++) {
+ String pkgName = mWhitelitsedPackagesForUserTypes.keyAt(i);
+ int whitelistedUserTypes = mWhitelitsedPackagesForUserTypes.valueAt(i);
+ if ((flags & whitelistedUserTypes) != 0) {
+ installablePkgs.add(pkgName);
+ }
+ }
+ return installablePkgs;
+ }
+
+ /**
+ * Set of package manifest names that are included anywhere in the package-to-user-type
+ * whitelist, as determined by SystemConfig.
+ *
+ * Packages that are whitelisted, but then blacklisted so that they aren't to be installed on
+ * any user, are still present in this list, since that is a valid scenario (e.g. if an OEM
+ * completely blacklists an AOSP app).
+ */
+ private Set<String> getWhitelistedSystemPackages() {
+ return mWhitelitsedPackagesForUserTypes.keySet();
+ }
+
+ /**
+ * Returns a map of package manifest names to the user flags on which it is to be installed.
+ * Also, clears this data from SystemConfig where it was stored inefficiently (and therefore
+ * should be called exactly once, even if the data isn't useful).
+ *
+ * Any system packages not present in this map should not even be on the device at all.
+ * To enforce this:
+ * <ul>
+ * <li>Illegal user types are ignored.</li>
+ * <li>Packages that never whitelisted at all (even if they are explicitly blacklisted) are
+ * ignored.</li>
+ * <li>Packages that are blacklisted whenever they are whitelisted will be stored with the
+ * flag 0 (since this is a valid scenario, e.g. if an OEM completely blacklists an AOSP
+ * app).</li>
+ * </ul>
+ */
+ @VisibleForTesting
+ static ArrayMap<String, Integer> determineWhitelistedPackagesForUserTypes(
+ SystemConfig sysConfig) {
+
+ final ArrayMap<String, Set<String>> whitelist =
+ sysConfig.getAndClearPackageToUserTypeWhitelist();
+ // result maps packageName -> userTypes on which the package should be installed.
+ final ArrayMap<String, Integer> result = new ArrayMap<>(whitelist.size() + 1);
+ // First, do the whitelisted user types.
+ for (int i = 0; i < whitelist.size(); i++) {
+ final String pkgName = whitelist.keyAt(i);
+ final int flags = getFlagsFromUserTypes(whitelist.valueAt(i));
+ if (flags != 0) {
+ result.put(pkgName, flags);
+ }
+ }
+ // Then, un-whitelist any blacklisted user types.
+ // TODO(b/141370854): Right now, the blacklist is actually just an 'unwhitelist'. Which
+ // direction we go depends on how we design user subtypes, which is still
+ // being designed. For now, unwhitelisting works for current use-cases.
+ final ArrayMap<String, Set<String>> blacklist =
+ sysConfig.getAndClearPackageToUserTypeBlacklist();
+ for (int i = 0; i < blacklist.size(); i++) {
+ final String pkgName = blacklist.keyAt(i);
+ final int nonFlags = getFlagsFromUserTypes(blacklist.valueAt(i));
+ final Integer flags = result.get(pkgName);
+ if (flags != null) {
+ result.put(pkgName, flags & ~nonFlags);
+ }
+ }
+ // Regardless of the whitelists/blacklists, ensure mandatory packages.
+ result.put("android",
+ UserInfo.FLAG_SYSTEM | UserInfo.FLAG_FULL | UserInfo.PROFILE_FLAGS_MASK);
+ return result;
+ }
+
+ /** Converts a user types, as used in SystemConfig, to a UserInfo flag. */
+ private static int getFlagsFromUserTypes(Iterable<String> userTypes) {
+ int flags = 0;
+ for (String type : userTypes) {
+ switch (type) {
+ case "GUEST":
+ flags |= UserInfo.FLAG_GUEST;
+ break;
+ case "RESTRICTED":
+ flags |= UserInfo.FLAG_RESTRICTED;
+ break;
+ case "MANAGED_PROFILE":
+ flags |= UserInfo.FLAG_MANAGED_PROFILE;
+ break;
+ case "EPHEMERAL":
+ flags |= UserInfo.FLAG_EPHEMERAL;
+ break;
+ case "DEMO":
+ flags |= UserInfo.FLAG_DEMO;
+ break;
+ case "FULL":
+ flags |= UserInfo.FLAG_FULL;
+ break;
+ case "SYSTEM":
+ flags |= UserInfo.FLAG_SYSTEM;
+ break;
+ case "PROFILE":
+ flags |= UserInfo.PROFILE_FLAGS_MASK;
+ break;
+ default:
+ Slog.w(TAG, "SystemConfig contained an invalid user type: " + type);
+ break;
+ // Other UserInfo flags are forbidden.
+ // In particular, FLAG_INITIALIZED, FLAG_DISABLED, FLAG_QUIET_MODE are inapplicable.
+ // The following are invalid now, but are reconsiderable: FLAG_PRIMARY, FLAG_ADMIN.
+ }
+ }
+ return flags;
+ }
+
+ void dump(PrintWriter pw) {
+ for (int i = 0; i < mWhitelitsedPackagesForUserTypes.size(); i++) {
+ final String pkgName = mWhitelitsedPackagesForUserTypes.keyAt(i);
+ final String whitelistedUserTypes =
+ UserInfo.flagsToString(mWhitelitsedPackagesForUserTypes.valueAt(i));
+ pw.println(" " + pkgName + ": " + whitelistedUserTypes);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/SystemConfigTest.java
new file mode 100644
index 0000000..ff03391
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/SystemConfigTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2019 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.server;
+
+import static org.junit.Assert.assertEquals;
+
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.Set;
+
+/**
+ * Tests for {@link SystemConfig}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:SystemConfigTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SystemConfigTest {
+ private static final String LOG_TAG = "SystemConfigTest";
+
+ private SystemConfig mSysConfig;
+
+ @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ @Before
+ public void setUp() throws Exception {
+ mSysConfig = new SystemConfigTestClass();
+ }
+
+ /**
+ * Subclass of SystemConfig without running the constructor.
+ */
+ private class SystemConfigTestClass extends SystemConfig {
+ SystemConfigTestClass() {
+ super(false);
+ }
+ }
+
+ /**
+ * Tests that readPermissions works correctly for the tag: install-in-user-type
+ */
+ @Test
+ public void testInstallInUserType() throws Exception {
+ final String contents1 =
+ "<permissions>\n"
+ + " <install-in-user-type package=\"com.android.package1\">\n"
+ + " <install-in user-type=\"FULL\" />\n"
+ + " <install-in user-type=\"PROFILE\" />\n"
+ + " </install-in-user-type>\n"
+ + " <install-in-user-type package=\"com.android.package2\">\n"
+ + " <install-in user-type=\"FULL\" />\n"
+ + " <install-in user-type=\"PROFILE\" />\n"
+ + " <do-not-install-in user-type=\"GUEST\" />\n"
+ + " </install-in-user-type>\n"
+ + "</permissions>";
+
+ final String contents2 =
+ "<permissions>\n"
+ + " <install-in-user-type package=\"com.android.package2\">\n"
+ + " <install-in user-type=\"SYSTEM\" />\n"
+ + " <do-not-install-in user-type=\"PROFILE\" />\n"
+ + " </install-in-user-type>\n"
+ + "</permissions>";
+
+ final String contents3 =
+ "<permissions>\n"
+ + " <install-in-user-type package=\"com.android.package2\">\n"
+ + " <install-in invalid-attribute=\"ADMIN\" />\n" // Ignore invalid attribute
+ + " </install-in-user-type>\n"
+ + " <install-in-user-type package=\"com.android.package2\">\n"
+ + " <install-in user-type=\"RESTRICTED\" />\n" // Valid
+ + " </install-in-user-type>\n"
+ + " <install-in-user-type>\n" // Ignored since missing package name
+ + " <install-in user-type=\"ADMIN\" />\n"
+ + " </install-in-user-type>\n"
+ + "</permissions>";
+
+ Map<String, Set<String>> expectedWhite = new ArrayMap<>();
+ expectedWhite.put("com.android.package1",
+ new ArraySet<>(Arrays.asList("FULL", "PROFILE")));
+ expectedWhite.put("com.android.package2",
+ new ArraySet<>(Arrays.asList("FULL", "PROFILE", "RESTRICTED", "SYSTEM")));
+
+ Map<String, Set<String>> expectedBlack = new ArrayMap<>();
+ expectedBlack.put("com.android.package2",
+ new ArraySet<>(Arrays.asList("GUEST", "PROFILE")));
+
+ final File folder1 = createTempSubfolder("folder1");
+ createTempFile(folder1, "permFile1.xml", contents1);
+
+ final File folder2 = createTempSubfolder("folder2");
+ createTempFile(folder2, "permFile2.xml", contents2);
+
+ // Also, make a third file, but with the name folder1/permFile2.xml, to prove no conflicts.
+ createTempFile(folder1, "permFile2.xml", contents3);
+
+ mSysConfig.readPermissions(folder1, /* No permission needed anyway */ 0);
+ mSysConfig.readPermissions(folder2, /* No permission needed anyway */ 0);
+
+ Map<String, Set<String>> actualWhite = mSysConfig.getAndClearPackageToUserTypeWhitelist();
+ Map<String, Set<String>> actualBlack = mSysConfig.getAndClearPackageToUserTypeBlacklist();
+
+ assertEquals("Whitelist was not cleared", 0,
+ mSysConfig.getAndClearPackageToUserTypeWhitelist().size());
+ assertEquals("Blacklist was not cleared", 0,
+ mSysConfig.getAndClearPackageToUserTypeBlacklist().size());
+
+ assertEquals("Incorrect whitelist.", expectedWhite, actualWhite);
+ assertEquals("Incorrect blacklist", expectedBlack, actualBlack);
+ }
+
+ /**
+ * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
+ * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
+ * @return the folder
+ */
+ private File createTempSubfolder(String folderName)
+ throws IOException {
+ File folder = new File(mTemporaryFolder.getRoot(), folderName);
+ folder.mkdir();
+ return folder;
+ }
+
+ /**
+ * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
+ * @param folder pre-existing subdirectory of mTemporaryFolder to put the file
+ * @param fileName name of the file (e.g. filename.xml) to create
+ * @param contents contents to write to the file
+ * @return the folder containing the newly created file (not the file itself!)
+ */
+ private File createTempFile(File folder, String fileName, String contents)
+ throws IOException {
+ File file = new File(folder, fileName);
+ BufferedWriter bw = new BufferedWriter(new FileWriter(file));
+ bw.write(contents);
+ bw.close();
+
+ // Print to logcat for test debugging.
+ Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath());
+ Scanner input = new Scanner(file);
+ while (input.hasNextLine()) {
+ Log.d(LOG_TAG, input.nextLine());
+ }
+
+ return folder;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
new file mode 100644
index 0000000..f0b0328
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2019 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.server.pm;
+
+import static android.content.pm.UserInfo.FLAG_FULL;
+import static android.content.pm.UserInfo.FLAG_GUEST;
+import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
+import static android.content.pm.UserInfo.FLAG_SYSTEM;
+
+import static com.android.server.pm.UserSystemPackageInstaller.PACKAGE_WHITELIST_MODE_PROP;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_LOG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.UserInfo;
+import android.os.Looper;
+import android.os.SystemProperties;
+import android.os.UserManager;
+import android.os.UserManagerInternal;
+import android.support.test.uiautomator.UiDevice;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for UserSystemPackageInstaller.
+ *
+ * <p>Run with:<pre>
+ * atest com.android.server.pm.UserSystemPackageInstallerTest
+ * </pre>
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class UserSystemPackageInstallerTest {
+ private static final String TAG = "UserSystemPackageInstallerTest";
+
+ private UserSystemPackageInstaller mUserSystemPackageInstaller;
+
+ private Context mContext;
+
+ /** Any users created during this test, for them to be removed when it's done. */
+ private final List<Integer> mRemoveUsers = new ArrayList<>();
+ /** Original value of PACKAGE_WHITELIST_MODE_PROP before the test, to reset at end. */
+ private final int mOriginalWhitelistMode = SystemProperties.getInt(
+ PACKAGE_WHITELIST_MODE_PROP, USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT);
+
+ @Before
+ public void setup() {
+ // Currently UserManagerService cannot be instantiated twice inside a VM without a cleanup
+ // TODO: Remove once UMS supports proper dependency injection
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ UserManagerService ums = new UserManagerService(InstrumentationRegistry.getContext());
+
+ mUserSystemPackageInstaller = new UserSystemPackageInstaller(ums);
+ mContext = InstrumentationRegistry.getTargetContext();
+ }
+
+ @After
+ public void tearDown() {
+ UserManager um = UserManager.get(mContext);
+ for (int userId : mRemoveUsers) {
+ um.removeUser(userId);
+ }
+ setUserTypePackageWhitelistMode(mOriginalWhitelistMode);
+ }
+
+ /**
+ * Subclass of SystemConfig without running the constructor.
+ */
+ private class SystemConfigTestClass extends SystemConfig {
+ SystemConfigTestClass(boolean readPermissions) {
+ super(readPermissions);
+ }
+ }
+
+ /**
+ * Test that determineWhitelistedPackagesForUserTypes reads SystemConfig information properly.
+ */
+ @Test
+ public void testDetermineWhitelistedPackagesForUserTypes() {
+ SystemConfig sysConfig = new SystemConfigTestClass(false) {
+ @Override
+ public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeWhitelist() {
+ ArrayMap<String, Set<String>> r = new ArrayMap<>();
+ r.put("com.android.package1", new ArraySet<>(Arrays.asList(
+ "PROFILE", "SYSTEM", "GUEST", "FULL", "invalid-garbage1")));
+ r.put("com.android.package2", new ArraySet<>(Arrays.asList(
+ "MANAGED_PROFILE")));
+ return r;
+ }
+
+ @Override
+ public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeBlacklist() {
+ ArrayMap<String, Set<String>> r = new ArrayMap<>();
+ r.put("com.android.package1", new ArraySet<>(Arrays.asList(
+ "FULL", "RESTRICTED", "invalid-garbage2")));
+ return r;
+ }
+ };
+
+ final ArrayMap<String, Integer> expectedOutput = getNewPackageToWhitelistedFlagsMap();
+ expectedOutput.put("com.android.package1",
+ UserInfo.PROFILE_FLAGS_MASK | FLAG_SYSTEM | FLAG_GUEST);
+ expectedOutput.put("com.android.package2",
+ UserInfo.FLAG_MANAGED_PROFILE);
+
+ final ArrayMap<String, Integer> actualOutput =
+ mUserSystemPackageInstaller.determineWhitelistedPackagesForUserTypes(sysConfig);
+
+ assertEquals("Incorrect package-to-user mapping.", expectedOutput, actualOutput);
+ }
+
+ /**
+ * Test that determineWhitelistedPackagesForUserTypes does not include packages that were never
+ * whitelisted properly, but does include packages that were whitelisted but then blacklisted.
+ */
+ @Test
+ public void testDetermineWhitelistedPackagesForUserTypes_noNetWhitelisting() {
+ SystemConfig sysConfig = new SystemConfigTestClass(false) {
+ @Override
+ public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeWhitelist() {
+ ArrayMap<String, Set<String>> r = new ArrayMap<>();
+ r.put("com.android.package1", new ArraySet<>(Arrays.asList("invalid1")));
+ // com.android.package2 has no whitelisting
+ r.put("com.android.package3", new ArraySet<>(Arrays.asList("PROFILE", "FULL")));
+ r.put("com.android.package4", new ArraySet<>(Arrays.asList("PROFILE")));
+ r.put("com.android.package5", new ArraySet<>());
+ // com.android.package6 has no whitelisting
+ return r;
+ }
+
+ @Override
+ public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeBlacklist() {
+ ArrayMap<String, Set<String>> r = new ArrayMap<>();
+ // com.android.package1 has no blacklisting
+ r.put("com.android.package2", new ArraySet<>(Arrays.asList("FULL")));
+ r.put("com.android.package3", new ArraySet<>(Arrays.asList("PROFILE", "FULL")));
+ r.put("com.android.package4", new ArraySet<>(Arrays.asList("PROFILE", "invalid4")));
+ // com.android.package5 has no blacklisting
+ r.put("com.android.package6", new ArraySet<>(Arrays.asList("invalid6")));
+ return r;
+ }
+ };
+
+ final ArrayMap<String, Integer> expectedOutput = getNewPackageToWhitelistedFlagsMap();
+ expectedOutput.put("com.android.package3", 0);
+ expectedOutput.put("com.android.package4", 0);
+
+ final ArrayMap<String, Integer> actualOutput =
+ mUserSystemPackageInstaller.determineWhitelistedPackagesForUserTypes(sysConfig);
+
+ assertEquals("Incorrect package-to-user mapping.", expectedOutput, actualOutput);
+ }
+
+ /**
+ * Tests that shouldInstallPackage correctly determines which packages should be installed.
+ */
+ @Test
+ public void testShouldInstallPackage() {
+ final String packageName1 = "pkg1"; // whitelisted
+ final String packageName2 = "pkg2"; // whitelisted and blacklisted
+ final String packageName3 = "pkg3"; // whitelisted for a different user type
+ final String packageName4 = "pkg4"; // not whitelisted nor blacklisted at all
+
+ final ArrayMap<String, Integer> pkgFlgMap = new ArrayMap<>(); // Whitelist: pkgs per flags
+ pkgFlgMap.put(packageName1, FLAG_FULL);
+ pkgFlgMap.put(packageName2, 0);
+ pkgFlgMap.put(packageName3, FLAG_MANAGED_PROFILE);
+
+ // Whitelist of pkgs for this specific user, i.e. subset of pkgFlagMap for this user.
+ final Set<String> userWhitelist = new ArraySet<>();
+ userWhitelist.add(packageName1);
+
+ final UserSystemPackageInstaller uspi = new UserSystemPackageInstaller(null, pkgFlgMap);
+
+ final PackageParser.Package pkg1 = new PackageParser.Package(packageName1);
+ final PackageParser.Package pkg2 = new PackageParser.Package(packageName2);
+ final PackageParser.Package pkg3 = new PackageParser.Package(packageName3);
+ final PackageParser.Package pkg4 = new PackageParser.Package(packageName4);
+
+ // No implicit whitelist, so only install pkg1.
+ boolean implicit = false;
+ boolean isSysUser = false;
+ assertTrue(uspi.shouldInstallPackage(pkg1, pkgFlgMap, userWhitelist, implicit, isSysUser));
+ assertFalse(uspi.shouldInstallPackage(pkg2, pkgFlgMap, userWhitelist, implicit, isSysUser));
+ assertFalse(uspi.shouldInstallPackage(pkg3, pkgFlgMap, userWhitelist, implicit, isSysUser));
+ assertFalse(uspi.shouldInstallPackage(pkg4, pkgFlgMap, userWhitelist, implicit, isSysUser));
+
+ // Use implicit whitelist, so install pkg1 and pkg4
+ implicit = true;
+ isSysUser = false;
+ assertTrue(uspi.shouldInstallPackage(pkg1, pkgFlgMap, userWhitelist, implicit, isSysUser));
+ assertFalse(uspi.shouldInstallPackage(pkg2, pkgFlgMap, userWhitelist, implicit, isSysUser));
+ assertFalse(uspi.shouldInstallPackage(pkg3, pkgFlgMap, userWhitelist, implicit, isSysUser));
+ assertTrue(uspi.shouldInstallPackage(pkg4, pkgFlgMap, userWhitelist, implicit, isSysUser));
+
+ // For user 0 specifically, we always implicitly whitelist.
+ implicit = false;
+ isSysUser = true;
+ assertTrue(uspi.shouldInstallPackage(pkg1, pkgFlgMap, userWhitelist, implicit, isSysUser));
+ assertFalse(uspi.shouldInstallPackage(pkg2, pkgFlgMap, userWhitelist, implicit, isSysUser));
+ assertFalse(uspi.shouldInstallPackage(pkg3, pkgFlgMap, userWhitelist, implicit, isSysUser));
+ assertTrue(uspi.shouldInstallPackage(pkg4, pkgFlgMap, userWhitelist, implicit, isSysUser));
+ }
+
+ /**
+ * Tests that getWhitelistedPackagesForUserType works properly, assuming that
+ * mWhitelistedPackagesForUserTypes (i.e. determineWhitelistedPackagesForUserTypes) is correct.
+ */
+ @Test
+ public void testGetWhitelistedPackagesForUserType() {
+ final String packageName1 = "pkg1"; // whitelisted for FULL
+ final String packageName2 = "pkg2"; // blacklisted whenever whitelisted
+ final String packageName3 = "pkg3"; // whitelisted for SYSTEM
+ final String packageName4 = "pkg4"; // whitelisted for FULL
+
+ final ArrayMap<String, Integer> pkgFlagMap = new ArrayMap<>(); // Whitelist: pkgs per flags
+ pkgFlagMap.put(packageName1, FLAG_FULL);
+ pkgFlagMap.put(packageName2, 0);
+ pkgFlagMap.put(packageName3, FLAG_SYSTEM);
+ pkgFlagMap.put(packageName4, FLAG_FULL);
+
+ // Whitelist of pkgs for this specific user, i.e. subset of pkgFlagMap for this user.
+ final Set<String> expectedUserWhitelist = new ArraySet<>();
+ expectedUserWhitelist.add(packageName1);
+
+ UserSystemPackageInstaller uspi = new UserSystemPackageInstaller(null, pkgFlagMap);
+
+ Set<String> output = uspi.getWhitelistedPackagesForUserType(FLAG_FULL);
+ assertEquals("Whitelist for FULL is the wrong size", 2, output.size());
+ assertTrue("Whitelist for FULL doesn't contain pkg1", output.contains(packageName1));
+ assertTrue("Whitelist for FULL doesn't contain pkg4", output.contains(packageName4));
+
+ output = uspi.getWhitelistedPackagesForUserType(FLAG_SYSTEM);
+ assertEquals("Whitelist for SYSTEM is the wrong size", 1, output.size());
+ assertTrue("Whitelist for SYSTEM doesn't contain pkg1", output.contains(packageName3));
+ }
+
+ /**
+ * Test that a newly created FULL user has the expected system packages.
+ *
+ * Assumes that SystemConfig and UserManagerService.determineWhitelistedPackagesForUserTypes
+ * work correctly (they are tested separately).
+ */
+ @Test
+ public void testPackagesForCreateUser_full() {
+ final int userFlags = UserInfo.FLAG_FULL;
+ setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
+ PackageManager pm = mContext.getPackageManager();
+
+ final SystemConfig sysConfig = new SystemConfigTestClass(true);
+ final ArrayMap<String, Integer> packageMap =
+ mUserSystemPackageInstaller.determineWhitelistedPackagesForUserTypes(sysConfig);
+ final Set<String> expectedPackages = new ArraySet<>(packageMap.size());
+ for (int i = 0; i < packageMap.size(); i++) {
+ if ((userFlags & packageMap.valueAt(i)) != 0) {
+ expectedPackages.add(packageMap.keyAt(i));
+ }
+ }
+
+ final UserManager um = UserManager.get(mContext);
+ final UserInfo user = um.createUser("Test User", userFlags);
+ assertNotNull(user);
+ mRemoveUsers.add(user.id);
+
+ final List<PackageInfo> packageInfos = pm.getInstalledPackagesAsUser(
+ PackageManager.MATCH_SYSTEM_ONLY
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS,
+ user.id);
+ final Set<String> actualPackages = new ArraySet<>(packageInfos.size());
+ for (PackageInfo p : packageInfos) {
+ actualPackages.add(p.packageName);
+ }
+ checkPackageDifferences(expectedPackages, actualPackages);
+ }
+
+ /** Asserts that actual is a subset of expected. */
+ private void checkPackageDifferences(Set<String> expected, Set<String> actual) {
+ final Set<String> uniqueToExpected = new ArraySet<>(expected);
+ uniqueToExpected.removeAll(actual);
+ final Set<String> uniqueToActual = new ArraySet<>(actual);
+ uniqueToActual.removeAll(expected);
+
+ Log.v(TAG, "Expected list uniquely has " + uniqueToExpected);
+ Log.v(TAG, "Actual list uniquely has " + uniqueToActual);
+
+ assertTrue("User's system packages includes non-whitelisted packages: " + uniqueToActual,
+ uniqueToActual.isEmpty());
+ }
+
+ /**
+ * Test that setEnableUserTypePackageWhitelist() has the correct effect.
+ */
+ @Test
+ public void testSetWhitelistEnabledMode() {
+ setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE);
+ assertFalse(mUserSystemPackageInstaller.isLogMode());
+ assertFalse(mUserSystemPackageInstaller.isEnforceMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+
+ setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_LOG);
+ assertTrue(mUserSystemPackageInstaller.isLogMode());
+ assertFalse(mUserSystemPackageInstaller.isEnforceMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+
+ setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
+ assertFalse(mUserSystemPackageInstaller.isLogMode());
+ assertTrue(mUserSystemPackageInstaller.isEnforceMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+
+ setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST);
+ assertFalse(mUserSystemPackageInstaller.isLogMode());
+ assertFalse(mUserSystemPackageInstaller.isEnforceMode());
+ assertTrue(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+
+ setUserTypePackageWhitelistMode(
+ USER_TYPE_PACKAGE_WHITELIST_MODE_LOG | USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
+ assertTrue(mUserSystemPackageInstaller.isLogMode());
+ assertTrue(mUserSystemPackageInstaller.isEnforceMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+
+ setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST
+ | USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
+ assertFalse(mUserSystemPackageInstaller.isLogMode());
+ assertTrue(mUserSystemPackageInstaller.isEnforceMode());
+ assertTrue(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ }
+
+ /** Sets the whitelist mode to the desired value via adb's setprop. */
+ private void setUserTypePackageWhitelistMode(int mode) {
+ UiDevice mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ try {
+ String result = mUiDevice.executeShellCommand(String.format("setprop %s %d",
+ PACKAGE_WHITELIST_MODE_PROP, mode));
+ assertFalse("Failed to set sysprop " + PACKAGE_WHITELIST_MODE_PROP + ": " + result,
+ result != null && result.contains("Failed"));
+ } catch (IOException e) {
+ fail("Failed to set sysprop " + PACKAGE_WHITELIST_MODE_PROP + ":\n" + e);
+ }
+ }
+
+ private ArrayMap<String, Integer> getNewPackageToWhitelistedFlagsMap() {
+ final ArrayMap<String, Integer> pkgFlagMap = new ArrayMap<>();
+ // "android" is always treated as whitelisted, regardless of the xml file.
+ pkgFlagMap.put("android", FLAG_SYSTEM | UserInfo.FLAG_FULL | UserInfo.PROFILE_FLAGS_MASK);
+ return pkgFlagMap;
+ }
+}