1 /*
2  * Copyright (C) 2015 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 
17 package com.android.packageinstaller.wear;
18 
19 import android.annotation.TargetApi;
20 import android.app.ActivityManager;
21 import android.content.ContentProvider;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.PackageInfo;
27 import android.content.pm.PackageManager;
28 import android.database.Cursor;
29 import android.net.Uri;
30 import android.os.Binder;
31 import android.os.Build;
32 import android.os.ParcelFileDescriptor;
33 import android.util.Log;
34 
35 import java.io.File;
36 import java.io.FileNotFoundException;
37 import java.util.List;
38 
39 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
40 
41 public class WearPackageIconProvider extends ContentProvider {
42     private static final String TAG = "WearPackageIconProvider";
43     public static final String AUTHORITY = "com.google.android.packageinstaller.wear.provider";
44 
45     private static final String REQUIRED_PERMISSION =
46             "com.google.android.permission.INSTALL_WEARABLE_PACKAGES";
47 
48     /** MIME types. */
49     public static final String ICON_TYPE = "vnd.android.cursor.item/cw_package_icon";
50 
51     @Override
onCreate()52     public boolean onCreate() {
53         return true;
54     }
55 
56     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)57     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
58             String sortOrder) {
59         throw new UnsupportedOperationException("Query is not supported.");
60     }
61 
62     @Override
getType(Uri uri)63     public String getType(Uri uri) {
64         if (uri == null) {
65             throw new IllegalArgumentException("URI passed in is null.");
66         }
67 
68         if (AUTHORITY.equals(uri.getEncodedAuthority())) {
69             return ICON_TYPE;
70         }
71         return null;
72     }
73 
74     @Override
insert(Uri uri, ContentValues values)75     public Uri insert(Uri uri, ContentValues values) {
76         throw new UnsupportedOperationException("Insert is not supported.");
77     }
78 
79     @Override
delete(Uri uri, String selection, String[] selectionArgs)80     public int delete(Uri uri, String selection, String[] selectionArgs) {
81         if (uri == null) {
82             throw new IllegalArgumentException("URI passed in is null.");
83         }
84 
85         enforcePermissions(uri);
86 
87         if (ICON_TYPE.equals(getType(uri))) {
88             final File file = WearPackageUtil.getIconFile(
89                     this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
90             if (file != null) {
91                 file.delete();
92             }
93         }
94 
95         return 0;
96     }
97 
98     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)99     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
100         throw new UnsupportedOperationException("Update is not supported.");
101     }
102 
103     @Override
openFile( Uri uri, @SuppressWarnings("unused") String mode)104     public ParcelFileDescriptor openFile(
105             Uri uri, @SuppressWarnings("unused") String mode) throws FileNotFoundException {
106         if (uri == null) {
107             throw new IllegalArgumentException("URI passed in is null.");
108         }
109 
110         enforcePermissions(uri);
111 
112         if (ICON_TYPE.equals(getType(uri))) {
113             final File file = WearPackageUtil.getIconFile(
114                     this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
115             if (file != null) {
116                 return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
117             }
118         }
119         return null;
120     }
121 
getUriForPackage(final String packageName)122     public static Uri getUriForPackage(final String packageName) {
123         return Uri.parse("content://" + AUTHORITY + "/icons/" + packageName + ".icon");
124     }
125 
getPackageNameFromUri(Uri uri)126     private String getPackageNameFromUri(Uri uri) {
127         if (uri == null) {
128             return null;
129         }
130         List<String> pathSegments = uri.getPathSegments();
131         String packageName = pathSegments.get(pathSegments.size() - 1);
132 
133         if (packageName.endsWith(".icon")) {
134             packageName = packageName.substring(0, packageName.lastIndexOf("."));
135         }
136         return packageName;
137     }
138 
139     /**
140      * Make sure the calling app is either a system app or the same app or has the right permission.
141      * @throws SecurityException if the caller has insufficient permissions.
142      */
143     @TargetApi(Build.VERSION_CODES.BASE_1_1)
enforcePermissions(Uri uri)144     private void enforcePermissions(Uri uri) {
145         // Redo some of the permission check in {@link ContentProvider}. Just add an extra check to
146         // allow System process to access this provider.
147         Context context = getContext();
148         final int pid = Binder.getCallingPid();
149         final int uid = Binder.getCallingUid();
150         final int myUid = android.os.Process.myUid();
151 
152         if (uid == myUid || isSystemApp(context, pid)) {
153             return;
154         }
155 
156         if (context.checkPermission(REQUIRED_PERMISSION, pid, uid) == PERMISSION_GRANTED) {
157             return;
158         }
159 
160         // last chance, check against any uri grants
161         if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
162                 == PERMISSION_GRANTED) {
163             return;
164         }
165 
166         throw new SecurityException("Permission Denial: reading "
167                 + getClass().getName() + " uri " + uri + " from pid=" + pid
168                 + ", uid=" + uid);
169     }
170 
171     /**
172      * From the pid of the calling process, figure out whether this is a system app or not. We do
173      * this by checking the application information corresponding to the pid and then checking if
174      * FLAG_SYSTEM is set.
175      */
176     @TargetApi(Build.VERSION_CODES.CUPCAKE)
isSystemApp(Context context, int pid)177     private boolean isSystemApp(Context context, int pid) {
178         // Get the Activity Manager Object
179         ActivityManager aManager =
180                 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
181         // Get the list of running Applications
182         List<ActivityManager.RunningAppProcessInfo> rapInfoList =
183                 aManager.getRunningAppProcesses();
184         for (ActivityManager.RunningAppProcessInfo rapInfo : rapInfoList) {
185             if (rapInfo.pid == pid) {
186                 try {
187                     PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(
188                             rapInfo.pkgList[0], 0);
189                     if (pkgInfo != null && pkgInfo.applicationInfo != null &&
190                             (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
191                         Log.d(TAG, pid + " is a system app.");
192                         return true;
193                     }
194                 } catch (PackageManager.NameNotFoundException e) {
195                     Log.e(TAG, "Could not find package information.", e);
196                     return false;
197                 }
198             }
199         }
200         return false;
201     }
202 }
203