1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.pm; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.content.pm.PackageInfo; 21 import android.content.pm.ShortcutInfo; 22 import android.graphics.Bitmap; 23 import android.util.AtomicFile; 24 import android.util.Slog; 25 import android.util.Xml; 26 27 import com.android.internal.annotations.GuardedBy; 28 import com.android.internal.util.Preconditions; 29 import com.android.modules.utils.TypedXmlSerializer; 30 import com.android.server.security.FileIntegrity; 31 32 import org.json.JSONException; 33 import org.json.JSONObject; 34 import org.xmlpull.v1.XmlPullParserException; 35 36 import java.io.File; 37 import java.io.FileOutputStream; 38 import java.io.IOException; 39 import java.nio.charset.StandardCharsets; 40 import java.util.Objects; 41 42 /** 43 * All methods should be either guarded by {@code #mShortcutUser.mService.mLock} or {@code #mLock}. 44 */ 45 abstract class ShortcutPackageItem { 46 private static final String TAG = ShortcutService.TAG; 47 private static final String KEY_NAME = "name"; 48 49 private final int mPackageUserId; 50 private final String mPackageName; 51 52 private final ShortcutPackageInfo mPackageInfo; 53 54 protected ShortcutUser mShortcutUser; 55 56 @GuardedBy("mLock") 57 protected ShortcutBitmapSaver mShortcutBitmapSaver; 58 59 protected final Object mLock = new Object(); 60 ShortcutPackageItem(@onNull ShortcutUser shortcutUser, int packageUserId, @NonNull String packageName, @NonNull ShortcutPackageInfo packageInfo)61 protected ShortcutPackageItem(@NonNull ShortcutUser shortcutUser, 62 int packageUserId, @NonNull String packageName, 63 @NonNull ShortcutPackageInfo packageInfo) { 64 mShortcutUser = shortcutUser; 65 mPackageUserId = packageUserId; 66 mPackageName = Preconditions.checkStringNotEmpty(packageName); 67 mPackageInfo = Objects.requireNonNull(packageInfo); 68 mShortcutBitmapSaver = new ShortcutBitmapSaver(shortcutUser.mService); 69 } 70 71 /** 72 * Change the parent {@link ShortcutUser}. Need it in the restore code. 73 */ replaceUser(ShortcutUser user)74 public void replaceUser(ShortcutUser user) { 75 mShortcutUser = user; 76 } 77 getUser()78 public ShortcutUser getUser() { 79 return mShortcutUser; 80 } 81 82 /** 83 * ID of the user who actually has this package running on. For {@link ShortcutPackage}, 84 * this is the same thing as {@link #getOwnerUserId}, but if it's a {@link ShortcutLauncher} and 85 * {@link #getOwnerUserId} is of work profile, then this ID is of the primary user. 86 */ getPackageUserId()87 public int getPackageUserId() { 88 return mPackageUserId; 89 } 90 91 /** 92 * ID of the user who sees the shortcuts from this instance. 93 */ getOwnerUserId()94 public abstract int getOwnerUserId(); 95 96 @NonNull getPackageName()97 public String getPackageName() { 98 return mPackageName; 99 } 100 getPackageInfo()101 public ShortcutPackageInfo getPackageInfo() { 102 return mPackageInfo; 103 } 104 refreshPackageSignatureAndSave()105 public void refreshPackageSignatureAndSave() { 106 if (mPackageInfo.isShadow()) { 107 return; // Don't refresh for shadow user. 108 } 109 final ShortcutService s = mShortcutUser.mService; 110 mPackageInfo.refreshSignature(s, this); 111 scheduleSave(); 112 } 113 attemptToRestoreIfNeededAndSave()114 public void attemptToRestoreIfNeededAndSave() { 115 if (!mPackageInfo.isShadow()) { 116 return; // Already installed, nothing to do. 117 } 118 final ShortcutService s = mShortcutUser.mService; 119 if (!s.isPackageInstalled(mPackageName, mPackageUserId)) { 120 if (ShortcutService.DEBUG) { 121 Slog.d(TAG, String.format("Package still not installed: %s/u%d", 122 mPackageName, mPackageUserId)); 123 } 124 return; // Not installed, no need to restore yet. 125 } 126 int restoreBlockReason; 127 long currentVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN; 128 129 if (!mPackageInfo.hasSignatures()) { 130 s.wtf("Attempted to restore package " + mPackageName + "/u" + mPackageUserId 131 + " but signatures not found in the restore data."); 132 restoreBlockReason = ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH; 133 } else { 134 final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId); 135 currentVersionCode = pi.getLongVersionCode(); 136 restoreBlockReason = mPackageInfo.canRestoreTo(s, pi, canRestoreAnyVersion()); 137 } 138 139 if (ShortcutService.DEBUG) { 140 Slog.d(TAG, String.format("Restoring package: %s/u%d (version=%d) %s for u%d", 141 mPackageName, mPackageUserId, currentVersionCode, 142 ShortcutInfo.getDisabledReasonDebugString(restoreBlockReason), 143 getOwnerUserId())); 144 } 145 146 onRestored(restoreBlockReason); 147 148 // Either way, it's no longer a shadow. 149 mPackageInfo.setShadow(false); 150 151 scheduleSave(); 152 } 153 canRestoreAnyVersion()154 protected abstract boolean canRestoreAnyVersion(); 155 onRestored(int restoreBlockReason)156 protected abstract void onRestored(int restoreBlockReason); 157 saveToXml(@onNull TypedXmlSerializer out, boolean forBackup)158 public abstract void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup) 159 throws IOException, XmlPullParserException; 160 161 @GuardedBy("mLock") saveToFileLocked(File path, boolean forBackup)162 public void saveToFileLocked(File path, boolean forBackup) { 163 final AtomicFile file = new AtomicFile(path); 164 FileOutputStream os = null; 165 try { 166 os = file.startWrite(); 167 168 // Write to XML 169 final TypedXmlSerializer itemOut; 170 if (forBackup) { 171 itemOut = Xml.newFastSerializer(); 172 itemOut.setOutput(os, StandardCharsets.UTF_8.name()); 173 } else { 174 itemOut = Xml.resolveSerializer(os); 175 } 176 itemOut.startDocument(null, true); 177 178 saveToXml(itemOut, forBackup); 179 180 itemOut.endDocument(); 181 182 os.flush(); 183 file.finishWrite(os); 184 185 try { 186 FileIntegrity.setUpFsVerity(path); 187 } catch (IOException e) { 188 Slog.e(TAG, "Failed to verity-protect " + path, e); 189 } 190 } catch (XmlPullParserException | IOException e) { 191 Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); 192 file.failWrite(os); 193 } 194 } 195 196 @GuardedBy("mLock") scheduleSaveToAppSearchLocked()197 void scheduleSaveToAppSearchLocked() { 198 199 } 200 dumpCheckin(boolean clear)201 public JSONObject dumpCheckin(boolean clear) throws JSONException { 202 final JSONObject result = new JSONObject(); 203 result.put(KEY_NAME, mPackageName); 204 return result; 205 } 206 207 /** 208 * Verify various internal states. 209 */ verifyStates()210 public void verifyStates() { 211 } 212 scheduleSave()213 public void scheduleSave() { 214 mShortcutUser.mService.injectPostToHandlerDebounced( 215 mSaveShortcutPackageRunner, mSaveShortcutPackageRunner); 216 } 217 218 private final Runnable mSaveShortcutPackageRunner = this::saveShortcutPackageItem; 219 saveShortcutPackageItem()220 void saveShortcutPackageItem() { 221 // Wait for bitmap saves to conclude before proceeding to saving shortcuts. 222 waitForBitmapSaves(); 223 // Save each ShortcutPackageItem in a separate Xml file. 224 final File path = getShortcutPackageItemFile(); 225 if (ShortcutService.DEBUG || ShortcutService.DEBUG_REBOOT) { 226 Slog.d(TAG, "Saving package item " + getPackageName() + " to " + path); 227 } 228 synchronized (mLock) { 229 path.getParentFile().mkdirs(); 230 // TODO: Since we are persisting shortcuts into AppSearch, we should read from/write to 231 // AppSearch as opposed to maintaining a separate XML file. 232 saveToFileLocked(path, false /*forBackup*/); 233 scheduleSaveToAppSearchLocked(); 234 } 235 } 236 waitForBitmapSaves()237 public boolean waitForBitmapSaves() { 238 synchronized (mLock) { 239 return mShortcutBitmapSaver.waitForAllSavesLocked(); 240 } 241 } 242 saveBitmap(ShortcutInfo shortcut, int maxDimension, Bitmap.CompressFormat format, int quality)243 public void saveBitmap(ShortcutInfo shortcut, 244 int maxDimension, Bitmap.CompressFormat format, int quality) { 245 synchronized (mLock) { 246 mShortcutBitmapSaver.saveBitmapLocked(shortcut, maxDimension, format, quality); 247 } 248 } 249 250 /** 251 * Wait for all pending saves to finish, and then return the given shortcut's bitmap path. 252 */ 253 @Nullable getBitmapPathMayWait(ShortcutInfo shortcut)254 public String getBitmapPathMayWait(ShortcutInfo shortcut) { 255 synchronized (mLock) { 256 return mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcut); 257 } 258 } 259 removeIcon(ShortcutInfo shortcut)260 public void removeIcon(ShortcutInfo shortcut) { 261 synchronized (mLock) { 262 mShortcutBitmapSaver.removeIcon(shortcut); 263 } 264 } 265 removeShortcutPackageItem()266 void removeShortcutPackageItem() { 267 synchronized (mLock) { 268 getShortcutPackageItemFile().delete(); 269 } 270 } 271 getShortcutPackageItemFile()272 protected abstract File getShortcutPackageItemFile(); 273 } 274