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