blob: e63f5365bff8bb530369c36cf9a7dac2a7790bbe [file] [log] [blame]
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -07001/*
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
17package com.android.server;
18
19import android.content.Context;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070020import android.content.pm.ActivityInfo;
21import android.content.pm.ApplicationInfo;
22import android.content.pm.PackageInfo;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070023import android.content.pm.PackageManager;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070024import android.content.pm.ResolveInfo;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070025import android.content.res.Resources;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070026import android.content.Intent;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070027import android.util.EventLog;
28import android.util.Slog;
29import android.os.Binder;
30import android.os.Build;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070031import android.provider.MediaStore;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070032import android.system.ErrnoException;
33import android.system.Os;
34import android.system.OsConstants;
35import android.system.StructStat;
36
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070037import com.android.internal.app.ResolverActivity;
38
39import dalvik.system.VMRuntime;
40
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070041import java.util.ArrayList;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070042import java.util.List;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070043import java.io.FileDescriptor;
44import java.io.FileOutputStream;
45import java.io.IOException;
46import java.io.PrintWriter;
47
48/**
49 * <p>PinnerService pins important files for key processes in memory.</p>
50 * <p>Files to pin are specified in the config_defaultPinnerServiceFiles
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070051 * overlay.</p>
52 * <p>Pin the default camera application if specified in config_pinnerCameraApp.</p>
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070053 */
54public final class PinnerService extends SystemService {
55 private static final boolean DEBUG = false;
56 private static final String TAG = "PinnerService";
57
58 private final Context mContext;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070059 private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<PinnedFile>();
60 private final ArrayList<PinnedFile> mPinnedCameraFiles = new ArrayList<PinnedFile>();
61 private final boolean mShouldPinCamera;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070062
63 private BinderService mBinderService;
64
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070065 private final long MAX_CAMERA_PIN_SIZE = 50 * (1 << 20); //50MB max
66
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070067
68 public PinnerService(Context context) {
69 super(context);
70
71 mContext = context;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070072 mShouldPinCamera = context.getResources().getBoolean(
73 com.android.internal.R.bool.config_pinnerCameraApp);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070074 }
75
76 @Override
77 public void onStart() {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070078 if (DEBUG) {
79 Slog.i(TAG, "Starting PinnerService");
80 }
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070081 mBinderService = new BinderService();
82 publishBinderService("pinner", mBinderService);
83
84 // Files to pin come from the overlay and can be specified per-device config
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070085 String[] filesToPin = mContext.getResources().getStringArray(
86 com.android.internal.R.array.config_defaultPinnerServiceFiles);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070087 // Continue trying to pin remaining files even if there is a failure
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070088 for (int i = 0; i < filesToPin.length; i++){
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070089 PinnedFile pf = pinFile(filesToPin[i], 0, 0, 0);
90 if (pf != null) {
91 mPinnedFiles.add(pf);
92 if (DEBUG) {
93 Slog.i(TAG, "Pinned file = " + pf.mFilename);
94 }
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070095 } else {
96 Slog.e(TAG, "Failed to pin file = " + filesToPin[i]);
97 }
98 }
99 }
100
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700101 /**
102 * Pin camera on unlock.
103 * We have to wait for unlock because the user's
104 * preference for camera is not available from PackageManager until after
105 * unlock
106 */
107 @Override
108 public void onUnlockUser(int userHandle) {
109 handlePin(userHandle);
110 }
111
112 /**
113 * Pin camera on user switch.
114 * If more than one user is using the device
115 * each user may set a different preference for the camera app.
116 * Make sure that user's preference is pinned into memory.
117 */
118 @Override
119 public void onSwitchUser(int userHandle) {
120 handlePin(userHandle);
121 }
122
123 private void handlePin(int userHandle) {
124 if (mShouldPinCamera) {
125 boolean success = pinCamera(userHandle);
126 if (!success) {
127 //this is not necessarily an error
128 if (DEBUG) {
129 Slog.v(TAG, "Failed to pin camera.");
130 }
131 }
132 }
133 }
134
135 /**
136 * determine if the camera app is already pinned by comparing the
137 * intent resolution to the pinned files list
138 */
139 private boolean alreadyPinned(int userHandle) {
140 ApplicationInfo cameraInfo = getCameraInfo(userHandle);
141 if (cameraInfo == null ) {
142 return false;
143 }
144 for (int i = 0; i < mPinnedCameraFiles.size(); i++) {
145 if (mPinnedCameraFiles.get(i).mFilename.equals(cameraInfo.sourceDir)) {
146 if (DEBUG) {
147 Slog.v(TAG, "Camera is already pinned");
148 }
149 return true;
150 }
151 }
152 return false;
153 }
154
155 private void unpinCameraApp() {
156 for (int i = 0; i < mPinnedCameraFiles.size(); i++) {
157 unpinFile(mPinnedCameraFiles.get(i));
158 }
159 mPinnedCameraFiles.clear();
160 }
161
162 private boolean isResolverActivity(ActivityInfo info) {
163 return ResolverActivity.class.getName().equals(info.name);
164 }
165
166 private ApplicationInfo getCameraInfo(int userHandle) {
167 // find the camera via an intent
168 // use INTENT_ACTION_STILL_IMAGE_CAMERA instead of _SECURE. On a
169 // device without a fbe enabled, the _SECURE intent will never get set.
170 Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
171 PackageManager pm = mContext.getPackageManager();
172 ResolveInfo cameraResolveInfo = pm.resolveActivityAsUser(
173 cameraIntent, PackageManager.MATCH_DEFAULT_ONLY, userHandle);
174 if (cameraResolveInfo == null ) {
175 //this is not necessarily an error
176 if (DEBUG) {
177 Slog.v(TAG, "Unable to resolve camera intent");
178 }
179 return null;
180 }
181
182 if (isResolverActivity(cameraResolveInfo.activityInfo))
183 {
184 return null;
185 }
186
187 return cameraResolveInfo.activityInfo.applicationInfo;
188 }
189
190 private boolean pinCamera(int userHandle){
191 //we may have already pinned a camera app. If we've pinned this
192 //camera app, we're done. otherwise, unpin and pin the new app
193 if (alreadyPinned(userHandle)){
194 return true;
195 }
196
197 ApplicationInfo cameraInfo = getCameraInfo(userHandle);
198 if (cameraInfo == null) {
199 return false;
200 }
201
202 //unpin after checking that the camera intent has resolved
203 //this prevents us from thrashing when switching users with
204 //FBE enabled, because the intent won't resolve until the unlock
205 unpinCameraApp();
206
207 //pin APK
208 String camAPK = cameraInfo.sourceDir;
209 PinnedFile pf = pinFile(camAPK, 0, 0, MAX_CAMERA_PIN_SIZE);
210 if (pf == null) {
211 Slog.e(TAG, "Failed to pin " + camAPK);
212 return false;
213 }
214 if (DEBUG) {
215 Slog.i(TAG, "Pinned " + pf.mFilename);
216 }
217 mPinnedCameraFiles.add(pf);
218
219 //find the location of the odex based on the location of the APK
220 int lastPeriod = camAPK.lastIndexOf('.');
221 int lastSlash = camAPK.lastIndexOf('/', lastPeriod);
222 String base = camAPK.substring(0, lastSlash);
223 String appName = camAPK.substring(lastSlash + 1, lastPeriod);
224
225 // determine the ABI from either ApplicationInfo or Build
226 String arch = "arm";
227 if (cameraInfo.primaryCpuAbi != null
228 && VMRuntime.is64BitAbi(cameraInfo.primaryCpuAbi)) {
229 arch = arch + "64";
230 } else {
231 if (VMRuntime.is64BitAbi(Build.SUPPORTED_ABIS[0])) {
232 arch = arch + "64";
233 }
234 }
235 String odex = base + "/oat/" + arch + "/" + appName + ".odex";
236 //not all apps have odex files, so not pinning the odex is not a fatal error
237 pf = pinFile(odex, 0, 0, MAX_CAMERA_PIN_SIZE);
238 if (pf != null) {
239 mPinnedCameraFiles.add(pf);
240 if (DEBUG) {
241 Slog.i(TAG, "Pinned " + pf.mFilename);
242 }
243 }
244 return true;
245 }
246
247
248 /** mlock length bytes of fileToPin in memory, starting at offset
249 * length == 0 means pin from offset to end of file
250 * maxSize == 0 means infinite
251 */
252 private static PinnedFile pinFile(String fileToPin, long offset, long length, long maxSize) {
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700253 FileDescriptor fd = new FileDescriptor();
254 try {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700255 fd = Os.open(fileToPin,
256 OsConstants.O_RDONLY | OsConstants.O_CLOEXEC | OsConstants.O_NOFOLLOW,
257 OsConstants.O_RDONLY);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700258
259 StructStat sb = Os.fstat(fd);
260
261 if (offset + length > sb.st_size) {
262 Os.close(fd);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700263 Slog.e(TAG, "Failed to pin file " + fileToPin +
264 ", request extends beyond end of file. offset + length = "
265 + (offset + length) + ", file length = " + sb.st_size);
266 return null;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700267 }
268
269 if (length == 0) {
270 length = sb.st_size - offset;
271 }
272
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700273 if (maxSize > 0 && length > maxSize) {
274 Slog.e(TAG, "Could not pin file " + fileToPin +
275 ", size = " + length + ", maxSize = " + maxSize);
276 Os.close(fd);
277 return null;
278 }
279
280 long address = Os.mmap(0, length, OsConstants.PROT_READ,
281 OsConstants.MAP_PRIVATE, fd, offset);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700282 Os.close(fd);
283
284 Os.mlock(address, length);
285
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700286 return new PinnedFile(address, length, fileToPin);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700287 } catch (ErrnoException e) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700288 Slog.e(TAG, "Could not pin file " + fileToPin + " with error " + e.getMessage());
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700289 if(fd.valid()) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700290 try {
291 Os.close(fd);
292 }
293 catch (ErrnoException eClose) {
294 Slog.e(TAG, "Failed to close fd, error = " + eClose.getMessage());
295 }
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700296 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700297 return null;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700298 }
299 }
300
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700301 private static boolean unpinFile(PinnedFile pf) {
302 try {
303 Os.munlock(pf.mAddress, pf.mLength);
304 } catch (ErrnoException e) {
305 Slog.e(TAG, "Failed to unpin file " + pf.mFilename + " with error " + e.getMessage());
306 return false;
307 }
308 if (DEBUG) {
309 Slog.i(TAG, "Unpinned file " + pf.mFilename );
310 }
311 return true;
312 }
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700313
314 private final class BinderService extends Binder {
315 @Override
316 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
317 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
318 pw.println("Pinned Files:");
319 for (int i = 0; i < mPinnedFiles.size(); i++) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700320 pw.println(mPinnedFiles.get(i).mFilename);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700321 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700322 for (int i = 0; i < mPinnedCameraFiles.size(); i++) {
323 pw.println(mPinnedCameraFiles.get(i).mFilename);
324 }
325 }
326 }
327
328 private static class PinnedFile {
329 long mAddress;
330 long mLength;
331 String mFilename;
332
333 PinnedFile(long address, long length, String filename) {
334 mAddress = address;
335 mLength = length;
336 mFilename = filename;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700337 }
338 }
339}