blob: 7ea89c9e8fcd931a363c9d229044e1f8ba67dfaa [file] [log] [blame]
/*
* Copyright (C) 2016 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.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import libcore.util.Objects;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.function.Consumer;
/**
* User information used by {@link ShortcutService}.
*
* All methods should be guarded by {@code #mService.mLock}.
*/
class ShortcutUser {
private static final String TAG = ShortcutService.TAG;
static final String TAG_ROOT = "user";
private static final String TAG_LAUNCHER = "launcher";
private static final String ATTR_VALUE = "value";
private static final String ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER = "locale-seq-no";
private static final String ATTR_LAST_APP_SCAN_TIME = "last-app-scan-time";
static final class PackageWithUser {
final int userId;
final String packageName;
private PackageWithUser(int userId, String packageName) {
this.userId = userId;
this.packageName = Preconditions.checkNotNull(packageName);
}
public static PackageWithUser of(int userId, String packageName) {
return new PackageWithUser(userId, packageName);
}
public static PackageWithUser of(ShortcutPackageItem spi) {
return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName());
}
@Override
public int hashCode() {
return packageName.hashCode() ^ userId;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof PackageWithUser)) {
return false;
}
final PackageWithUser that = (PackageWithUser) obj;
return userId == that.userId && packageName.equals(that.packageName);
}
@Override
public String toString() {
return String.format("[Package: %d, %s]", userId, packageName);
}
}
final ShortcutService mService;
@UserIdInt
private final int mUserId;
private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
/** Default launcher that can access the launcher apps APIs. */
private ComponentName mDefaultLauncherComponent;
private long mKnownLocaleChangeSequenceNumber;
private long mLastAppScanTime;
public ShortcutUser(ShortcutService service, int userId) {
mService = service;
mUserId = userId;
}
public int getUserId() {
return mUserId;
}
public long getLastAppScanTime() {
return mLastAppScanTime;
}
public void setLastAppScanTime(long lastAppScanTime) {
mLastAppScanTime = lastAppScanTime;
}
// We don't expose this directly to non-test code because only ShortcutUser should add to/
// remove from it.
@VisibleForTesting
ArrayMap<String, ShortcutPackage> getAllPackagesForTest() {
return mPackages;
}
public boolean hasPackage(@NonNull String packageName) {
return mPackages.containsKey(packageName);
}
public ShortcutPackage removePackage(@NonNull String packageName) {
final ShortcutPackage removed = mPackages.remove(packageName);
mService.cleanupBitmapsForPackage(mUserId, packageName);
return removed;
}
// We don't expose this directly to non-test code because only ShortcutUser should add to/
// remove from it.
@VisibleForTesting
ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() {
return mLaunchers;
}
public void addLauncher(ShortcutLauncher launcher) {
mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(),
launcher.getPackageName()), launcher);
}
@Nullable
public ShortcutLauncher removeLauncher(
@UserIdInt int packageUserId, @NonNull String packageName) {
return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
}
@Nullable
public ShortcutPackage getPackageShortcutsIfExists(@NonNull String packageName) {
final ShortcutPackage ret = mPackages.get(packageName);
if (ret != null) {
ret.attemptToRestoreIfNeededAndSave();
}
return ret;
}
@NonNull
public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
ShortcutPackage ret = getPackageShortcutsIfExists(packageName);
if (ret == null) {
ret = new ShortcutPackage(this, mUserId, packageName);
mPackages.put(packageName, ret);
}
return ret;
}
@NonNull
public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName,
@UserIdInt int launcherUserId) {
final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
ShortcutLauncher ret = mLaunchers.get(key);
if (ret == null) {
ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
mLaunchers.put(key, ret);
} else {
ret.attemptToRestoreIfNeededAndSave();
}
return ret;
}
public void forAllPackages(Consumer<? super ShortcutPackage> callback) {
final int size = mPackages.size();
for (int i = 0; i < size; i++) {
callback.accept(mPackages.valueAt(i));
}
}
public void forAllLaunchers(Consumer<? super ShortcutLauncher> callback) {
final int size = mLaunchers.size();
for (int i = 0; i < size; i++) {
callback.accept(mLaunchers.valueAt(i));
}
}
public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) {
forAllLaunchers(callback);
forAllPackages(callback);
}
public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId,
Consumer<ShortcutPackageItem> callback) {
forAllPackageItems(spi -> {
if ((spi.getPackageUserId() == packageUserId)
&& spi.getPackageName().equals(packageName)) {
callback.accept(spi);
}
});
}
/**
* Reset all throttling counters for all packages, if there has been a system locale change.
*/
public void resetThrottlingIfNeeded() {
final long currentNo = mService.getLocaleChangeSequenceNumber();
if (mKnownLocaleChangeSequenceNumber < currentNo) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, "LocaleChange detected for user " + mUserId);
}
mKnownLocaleChangeSequenceNumber = currentNo;
forAllPackages(p -> p.resetRateLimiting());
mService.scheduleSaveUser(mUserId);
}
}
public void handlePackageAddedOrUpdated(@NonNull String packageName, boolean forceRescan) {
final boolean isNewApp = !mPackages.containsKey(packageName);
final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp, forceRescan)) {
if (isNewApp) {
mPackages.remove(packageName);
}
}
}
public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
@UserIdInt int packageUserId) {
forPackageItem(packageName, packageUserId, spi -> {
spi.attemptToRestoreIfNeededAndSave();
});
}
public void saveToXml(XmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException {
out.startTag(null, TAG_ROOT);
ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER,
mKnownLocaleChangeSequenceNumber);
ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
mLastAppScanTime);
ShortcutService.writeTagValue(out, TAG_LAUNCHER,
mDefaultLauncherComponent);
// Can't use forEachPackageItem due to the checked exceptions.
{
final int size = mLaunchers.size();
for (int i = 0; i < size; i++) {
saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup);
}
}
{
final int size = mPackages.size();
for (int i = 0; i < size; i++) {
saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup);
}
}
out.endTag(null, TAG_ROOT);
}
private void saveShortcutPackageItem(XmlSerializer out,
ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
if (forBackup) {
if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
return; // Don't save.
}
if (spi.getPackageUserId() != spi.getOwnerUserId()) {
return; // Don't save cross-user information.
}
}
spi.saveToXml(out, forBackup);
}
public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId,
boolean fromBackup) throws IOException, XmlPullParserException {
final ShortcutUser ret = new ShortcutUser(s, userId);
ret.mKnownLocaleChangeSequenceNumber = ShortcutService.parseLongAttribute(parser,
ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER);
// If lastAppScanTime is in the future, that means the clock went backwards.
// Just scan all apps again.
final long lastAppScanTime = ShortcutService.parseLongAttribute(parser,
ATTR_LAST_APP_SCAN_TIME);
final long currentTime = s.injectCurrentTimeMillis();
ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0;
final int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final int depth = parser.getDepth();
final String tag = parser.getName();
if (depth == outerDepth + 1) {
switch (tag) {
case TAG_LAUNCHER: {
ret.mDefaultLauncherComponent = ShortcutService.parseComponentNameAttribute(
parser, ATTR_VALUE);
continue;
}
case ShortcutPackage.TAG_ROOT: {
final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
s, ret, parser, fromBackup);
// Don't use addShortcut(), we don't need to save the icon.
ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
continue;
}
case ShortcutLauncher.TAG_ROOT: {
ret.addLauncher(
ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup));
continue;
}
}
}
ShortcutService.warnForInvalidTag(depth, tag);
}
return ret;
}
public ComponentName getDefaultLauncherComponent() {
return mDefaultLauncherComponent;
}
public void setDefaultLauncherComponent(ComponentName launcherComponent) {
if (Objects.equal(mDefaultLauncherComponent, launcherComponent)) {
return;
}
mDefaultLauncherComponent = launcherComponent;
mService.scheduleSaveUser(mUserId);
}
public void resetThrottling() {
for (int i = mPackages.size() - 1; i >= 0; i--) {
mPackages.valueAt(i).resetThrottling();
}
}
public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
pw.print(prefix);
pw.print("User: ");
pw.print(mUserId);
pw.print(" Known locale seq#: ");
pw.print(mKnownLocaleChangeSequenceNumber);
pw.print(" Last app scan: [");
pw.print(mLastAppScanTime);
pw.print("] ");
pw.print(ShortcutService.formatTime(mLastAppScanTime));
pw.println();
prefix += prefix + " ";
pw.print(prefix);
pw.print("Default launcher: ");
pw.print(mDefaultLauncherComponent);
pw.println();
for (int i = 0; i < mLaunchers.size(); i++) {
mLaunchers.valueAt(i).dump(pw, prefix);
}
for (int i = 0; i < mPackages.size(); i++) {
mPackages.valueAt(i).dump(pw, prefix);
}
pw.println();
pw.print(prefix);
pw.println("Bitmap directories: ");
dumpDirectorySize(pw, prefix + " ", mService.getUserBitmapFilePath(mUserId));
}
private void dumpDirectorySize(@NonNull PrintWriter pw,
@NonNull String prefix, File path) {
int numFiles = 0;
long size = 0;
final File[] children = path.listFiles();
if (children != null) {
for (File child : path.listFiles()) {
if (child.isFile()) {
numFiles++;
size += child.length();
} else if (child.isDirectory()) {
dumpDirectorySize(pw, prefix + " ", child);
}
}
}
pw.print(prefix);
pw.print("Path: ");
pw.print(path.getName());
pw.print("/ has ");
pw.print(numFiles);
pw.print(" files, size=");
pw.print(size);
pw.print(" (");
pw.print(Formatter.formatFileSize(mService.mContext, size));
pw.println(")");
}
}