blob: a10aed5e3b0d96fd6711f606b3283d4be09a4dc8 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.bedstead.nene.packages;
import static android.os.Build.VERSION.SDK_INT;
import static com.android.bedstead.nene.users.User.UserState.RUNNING_UNLOCKED;
import androidx.annotation.Nullable;
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.exceptions.AdbException;
import com.android.bedstead.nene.exceptions.AdbParseException;
import com.android.bedstead.nene.exceptions.NeneException;
import com.android.bedstead.nene.users.User;
import com.android.bedstead.nene.users.UserReference;
import com.android.bedstead.nene.utils.ShellCommand;
import com.android.bedstead.nene.utils.ShellCommandUtils;
import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Test APIs relating to packages.
*/
public final class Packages {
private Map<String, Package> mCachedPackages = null;
private Set<String> mFeatures = null;
private final AdbPackageParser mParser = AdbPackageParser.get(this, SDK_INT);
final TestApis mTestApis;
public Packages(TestApis testApis) {
if (testApis == null) {
throw new NullPointerException();
}
mTestApis = testApis;
}
/** Get the features available on the device. */
public Set<String> features() {
if (mFeatures == null) {
fillCache();
}
return mFeatures;
}
/** Resolve all packages on the device. */
public Collection<Package> all() {
fillCache();
return mCachedPackages.values();
}
/** Resolve all packages installed for a given {@link UserReference}. */
public Collection<Package> installedForUser(UserReference user) {
if (user == null) {
throw new NullPointerException();
}
Set<Package> installedForUser = new HashSet<>();
for (Package pkg : all()) {
if (pkg.installedOnUsers().contains(user)) {
installedForUser.add(pkg);
}
}
return installedForUser;
}
/**
* Install an APK file to a given {@link UserReference}.
*
* <p>The user must be started.
*
* <p>If the package is already installed, this will replace it.
*/
public void install(UserReference user, File apkFile) {
if (user == null || apkFile == null) {
throw new NullPointerException();
}
User resolvedUser = user.resolve();
// TODO(scottjonathan): Consider if it's worth the additional call here - we could
// optionally instead timeout the shell command (it doesn't respond if the user isn't
// started)
if (resolvedUser == null || resolvedUser.state() != RUNNING_UNLOCKED) {
throw new NeneException("Packages can not be installed in non-started users "
+ "(Trying to install into user " + resolvedUser + ")");
}
// By default when using ADB we don't know the package name of the file upon success.
// we could make an additional call to get it (either parsing all installed and finding the
// one matching the apk, or by trying to install again and parsing the error - this would
// only work before P because after P there isn't an error) - but that
// would mean we are making two adb calls rather than one - needs to be decided.
try {
// Expected output "Success"
ShellCommand.builderForUser(user, "pm install")
.addOperand("-r") // Reinstall automatically
.addOperand(apkFile.getAbsolutePath())
.executeAndValidateOutput(ShellCommandUtils::startsWithSuccess);
} catch (AdbException e) {
throw new NeneException("Could not install " + apkFile + " for user " + user, e);
}
}
@Nullable
Package fetchPackage(String packageName) {
// TODO(scottjonathan): fillCache probably does more than we need here -
// can we make it more efficient?
fillCache();
Package pkg = mCachedPackages.get(packageName);
if (pkg == null || pkg.installedOnUsers().isEmpty()) {
return null; // Treat it as uninstalled once all users are removed/removing
}
return pkg;
}
/**
* Get a reference to a package with the given {@code packageName}.
*
* <p>This does not guarantee that the package exists. Call {@link PackageReference#resolve()}
* to find specific details about the package on the device.
*/
public PackageReference find(String packageName) {
if (packageName == null) {
throw new NullPointerException();
}
return new UnresolvedPackage(this, packageName);
}
private void fillCache() {
try {
// TODO: Replace use of adb on supported versions of Android
String packageDumpsysOutput = ShellCommandUtils.executeCommand("dumpsys package");
AdbPackageParser.ParseResult result = mParser.parse(packageDumpsysOutput);
mCachedPackages = result.mPackages;
mFeatures = result.mFeatures;
} catch (AdbException | AdbParseException e) {
throw new RuntimeException("Error filling cache", e);
}
}
}