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