DefaultContainerService.java revision 63d15ee6ae4ff5079c7551ca4131c6d7f23ad91c
1package com.android.defcontainer;
2
3import com.android.internal.app.IMediaContainerService;
4
5import android.content.Intent;
6import android.net.Uri;
7import android.os.Debug;
8import android.os.IBinder;
9import android.os.IMountService;
10import android.os.MountServiceResultCode;
11import android.os.ParcelFileDescriptor;
12import android.os.Process;
13import android.os.RemoteException;
14import android.os.ServiceManager;
15import android.app.Service;
16import android.util.Log;
17
18import java.io.File;
19import java.io.FileInputStream;
20import java.io.FileNotFoundException;
21import java.io.FileOutputStream;
22import java.io.IOException;
23import java.io.InputStream;
24import java.io.OutputStream;
25
26import android.os.FileUtils;
27
28
29/*
30 * This service copies a downloaded apk to a file passed in as
31 * a ParcelFileDescriptor or to a newly created container specified
32 * by parameters. The DownloadManager gives access to this process
33 * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER
34 * permission to access apks downloaded via the download manager.
35 */
36public class DefaultContainerService extends Service {
37    private static final String TAG = "DefContainer";
38    private static final boolean localLOGV = false;
39
40    private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
41        /*
42         * Creates a new container and copies resource there.
43         * @param paackageURI the uri of resource to be copied. Can be either
44         * a content uri or a file uri
45         * @param containerId the id of the secure container that should
46         * be used for creating a secure container into which the resource
47         * will be copied.
48         * @param key Refers to key used for encrypting the secure container
49         * @param resFileName Name of the target resource file(relative to newly
50         * created secure container)
51         * @return Returns the new cache path where the resource has been copied into
52         *
53         */
54        public String copyResourceToContainer(final Uri packageURI,
55                final String containerId,
56                final String key, final String resFileName) {
57            if (packageURI == null || containerId == null) {
58                return null;
59            }
60            return copyResourceInner(packageURI, containerId, key, resFileName);
61        }
62
63        /*
64         * Copy specified resource to output stream
65         * @param packageURI the uri of resource to be copied. Should be a
66         * file uri
67         * @param outStream Remote file descriptor to be used for copying
68         * @return Returns true if copy succeded or false otherwise.
69         */
70        public boolean copyResource(final Uri packageURI,
71                ParcelFileDescriptor outStream) {
72            if (packageURI == null ||  outStream == null) {
73                return false;
74            }
75            ParcelFileDescriptor.AutoCloseOutputStream
76            autoOut = new ParcelFileDescriptor.AutoCloseOutputStream(outStream);
77            return copyFile(packageURI, autoOut);
78        }
79    };
80
81    public IBinder onBind(Intent intent) {
82        return mBinder;
83    }
84
85    private IMountService getMountService() {
86        return IMountService.Stub.asInterface(ServiceManager.getService("mount"));
87    }
88
89    private String copyResourceInner(Uri packageURI, String newCacheId, String key, String resFileName) {
90        // Create new container at newCachePath
91        String codePath = packageURI.getPath();
92        String newCachePath = null;
93        final int CREATE_FAILED = 1;
94        final int COPY_FAILED = 2;
95        final int FINALIZE_FAILED = 3;
96        final int PASS = 4;
97        int errCode = CREATE_FAILED;
98        // Create new container
99        if ((newCachePath = createSdDir(packageURI, newCacheId, key)) != null) {
100            File resFile = new File(newCachePath, resFileName);
101            errCode = COPY_FAILED;
102            if (localLOGV) Log.i(TAG, "Trying to copy " + codePath + " to " + resFile);
103            // Copy file from codePath
104            if (FileUtils.copyFile(new File(codePath), resFile)) {
105                errCode = FINALIZE_FAILED;
106                if (finalizeSdDir(newCacheId)) {
107                    errCode = PASS;
108                }
109            }
110        }
111        // Print error based on errCode
112        String errMsg = "";
113        switch (errCode) {
114            case CREATE_FAILED:
115                errMsg = "CREATE_FAILED";
116                break;
117            case COPY_FAILED:
118                errMsg = "COPY_FAILED";
119                destroySdDir(newCacheId);
120                break;
121            case FINALIZE_FAILED:
122                errMsg = "FINALIZE_FAILED";
123                destroySdDir(newCacheId);
124                break;
125            default:
126                errMsg = "PASS";
127                unMountSdDir(newCacheId);
128                break;
129        }
130        Log.i(TAG, "Status: " + errMsg);
131        if (errCode != PASS) {
132            return null;
133        }
134        return newCachePath;
135    }
136
137    private String createSdDir(final Uri packageURI,
138            String containerId, String sdEncKey) {
139        File tmpPackageFile = new File(packageURI.getPath());
140        // Create mount point via MountService
141        IMountService mountService = getMountService();
142        long len = tmpPackageFile.length();
143        int mbLen = (int) (len/(1024*1024));
144        if ((len - (mbLen * 1024 * 1024)) > 0) {
145            mbLen++;
146        }
147        if (localLOGV) Log.i(TAG, "mbLen="+mbLen);
148        String cachePath = null;
149        int ownerUid = Process.myUid();
150        try {
151            int rc = mountService.createSecureContainer(
152                    containerId, mbLen, "vfat", sdEncKey, ownerUid);
153
154            if (rc != MountServiceResultCode.OperationSucceeded) {
155                Log.e(TAG, String.format("Container creation failed (%d)", rc));
156
157                // XXX: This destroy should not be necessary
158                rc = mountService.destroySecureContainer(containerId);
159                if (rc != MountServiceResultCode.OperationSucceeded) {
160                    Log.e(TAG, String.format("Container creation-cleanup failed (%d)", rc));
161                    return null;
162                }
163
164                // XXX: Does this ever actually succeed?
165                rc = mountService.createSecureContainer(
166                        containerId, mbLen, "vfat", sdEncKey, ownerUid);
167                if (rc != MountServiceResultCode.OperationSucceeded) {
168                    Log.e(TAG, String.format("Container creation retry failed (%d)", rc));
169                }
170            }
171
172            cachePath = mountService.getSecureContainerPath(containerId);
173            if (localLOGV) Log.i(TAG, "Trying to create secure container for  "
174                    + containerId + ", cachePath =" + cachePath);
175            return cachePath;
176        } catch(RemoteException e) {
177            Log.e(TAG, "MountService not running?");
178            return null;
179        }
180    }
181
182    private boolean destroySdDir(String containerId) {
183        try {
184            // We need to destroy right away
185            getMountService().destroySecureContainer(containerId);
186            return true;
187        } catch (IllegalStateException e) {
188            Log.i(TAG, "Failed to destroy container : " + containerId);
189        } catch(RemoteException e) {
190            Log.e(TAG, "MountService not running?");
191        }
192        return false;
193    }
194
195    private boolean finalizeSdDir(String containerId){
196        try {
197            getMountService().finalizeSecureContainer(containerId);
198            return true;
199        } catch (IllegalStateException e) {
200            Log.i(TAG, "Failed to finalize container for pkg : " + containerId);
201        } catch(RemoteException e) {
202            Log.e(TAG, "MountService not running?");
203        }
204        return false;
205    }
206
207    private boolean unMountSdDir(String containerId) {
208        try {
209            getMountService().unmountSecureContainer(containerId);
210            return true;
211        } catch (IllegalStateException e) {
212            Log.e(TAG, "Failed to unmount id:  " + containerId + " with exception " + e);
213        } catch(RemoteException e) {
214            Log.e(TAG, "MountService not running?");
215        }
216        return false;
217    }
218
219    private String mountSdDir(String containerId, String key) {
220        try {
221            int rc = getMountService().mountSecureContainer(containerId, key, Process.myUid());
222            if (rc == MountServiceResultCode.OperationSucceeded) {
223                return getMountService().getSecureContainerPath(containerId);
224            } else {
225                Log.e(TAG, String.format("Failed to mount id %s with rc %d ", containerId, rc));
226            }
227        } catch(RemoteException e) {
228            Log.e(TAG, "MountService not running?");
229        }
230        return null;
231    }
232
233    public static boolean copyToFile(InputStream inputStream, FileOutputStream out) {
234        try {
235            byte[] buffer = new byte[4096];
236            int bytesRead;
237            while ((bytesRead = inputStream.read(buffer)) >= 0) {
238                out.write(buffer, 0, bytesRead);
239            }
240            return true;
241        } catch (IOException e) {
242            Log.i(TAG, "Exception : " + e + " when copying file");
243            return false;
244        }
245    }
246
247    public static boolean copyToFile(File srcFile, FileOutputStream out) {
248        InputStream inputStream = null;
249        try {
250            inputStream = new FileInputStream(srcFile);
251            return copyToFile(inputStream, out);
252        } catch (IOException e) {
253            return false;
254        } finally {
255            try { if (inputStream != null) inputStream.close(); } catch (IOException e) {}
256        }
257    }
258
259    private  boolean copyFile(Uri pPackageURI, FileOutputStream outStream) {
260        if (pPackageURI.getScheme().equals("file")) {
261            final File srcPackageFile = new File(pPackageURI.getPath());
262            // We copy the source package file to a temp file and then rename it to the
263            // destination file in order to eliminate a window where the package directory
264            // scanner notices the new package file but it's not completely copied yet.
265            if (!copyToFile(srcPackageFile, outStream)) {
266                Log.e(TAG, "Couldn't copy file: " + srcPackageFile);
267                return false;
268            }
269        } else if (pPackageURI.getScheme().equals("content")) {
270            ParcelFileDescriptor fd = null;
271            try {
272                fd = getContentResolver().openFileDescriptor(pPackageURI, "r");
273            } catch (FileNotFoundException e) {
274                Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e);
275                return false;
276            }
277            if (fd == null) {
278                Log.e(TAG, "Couldn't open file descriptor from download service (null).");
279                return false;
280            } else {
281                if (localLOGV) {
282                    Log.v(TAG, "Opened file descriptor from download service.");
283                }
284                ParcelFileDescriptor.AutoCloseInputStream
285                dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd);
286                // We copy the source package file to a temp file and then rename it to the
287                // destination file in order to eliminate a window where the package directory
288                // scanner notices the new package file but it's not completely copied yet.
289                if (!copyToFile(dlStream, outStream)) {
290                    Log.e(TAG, "Couldn't copy " + pPackageURI + " to temp file.");
291                    return false;
292                }
293            }
294        } else {
295            Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI);
296            return false;
297        }
298        return true;
299    }
300}
301