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