• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 **
3 ** Copyright 2007, The Android Open Source Project
4 **
5 ** Licensed under the Apache License, Version 2.0 (the "License");
6 ** you may not use this file except in compliance with the License.
7 ** You may obtain a copy of the License at
8 **
9 **     http://www.apache.org/licenses/LICENSE-2.0
10 **
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 */
17 package com.android.packageinstaller;
18 
19 import android.app.Activity;
20 import android.app.ActivityManagerNative;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.DialogInterface.OnCancelListener;
26 import android.content.Intent;
27 import android.content.SharedPreferences;
28 import android.content.pm.ApplicationInfo;
29 import android.content.pm.ManifestDigest;
30 import android.content.pm.PackageInfo;
31 import android.content.pm.PackageManager;
32 import android.content.pm.PackageManager.NameNotFoundException;
33 import android.content.pm.PackageParser;
34 import android.content.pm.PackageUserState;
35 import android.content.pm.VerificationParams;
36 import android.net.Uri;
37 import android.os.Bundle;
38 import android.provider.Settings;
39 import android.support.v4.view.ViewPager;
40 import android.util.Log;
41 import android.view.LayoutInflater;
42 import android.view.View;
43 import android.view.View.OnClickListener;
44 import android.view.ViewGroup;
45 import android.widget.AppSecurityPermissions;
46 import android.widget.Button;
47 import android.widget.TabHost;
48 import android.widget.TextView;
49 
50 import java.io.File;
51 
52 /*
53  * This activity is launched when a new application is installed via side loading
54  * The package is first parsed and the user is notified of parse errors via a dialog.
55  * If the package is successfully parsed, the user is notified to turn on the install unknown
56  * applications setting. A memory check is made at this point and the user is notified of out
57  * of memory conditions if any. If the package is already existing on the device,
58  * a confirmation dialog (to replace the existing package) is presented to the user.
59  * Based on the user response the package is then installed by launching InstallAppConfirm
60  * sub activity. All state transitions are handled in this activity
61  */
62 public class PackageInstallerActivity extends Activity implements OnCancelListener, OnClickListener {
63     private static final String TAG = "PackageInstaller";
64     private Uri mPackageURI;
65     private Uri mOriginatingURI;
66     private Uri mReferrerURI;
67     private int mOriginatingUid = VerificationParams.NO_UID;
68     private ManifestDigest mPkgDigest;
69 
70     private boolean localLOGV = false;
71     PackageManager mPm;
72     PackageInfo mPkgInfo;
73     ApplicationInfo mSourceInfo;
74 
75     // ApplicationInfo object primarily used for already existing applications
76     private ApplicationInfo mAppInfo = null;
77 
78     // View for install progress
79     View mInstallConfirm;
80     // Buttons to indicate user acceptance
81     private Button mOk;
82     private Button mCancel;
83     CaffeinatedScrollView mScrollView = null;
84     private boolean mOkCanInstall = false;
85 
86     static final String PREFS_ALLOWED_SOURCES = "allowed_sources";
87 
88     // Dialog identifiers used in showDialog
89     private static final int DLG_BASE = 0;
90     private static final int DLG_UNKNOWN_APPS = DLG_BASE + 1;
91     private static final int DLG_PACKAGE_ERROR = DLG_BASE + 2;
92     private static final int DLG_OUT_OF_SPACE = DLG_BASE + 3;
93     private static final int DLG_INSTALL_ERROR = DLG_BASE + 4;
94     private static final int DLG_ALLOW_SOURCE = DLG_BASE + 5;
95 
startInstallConfirm()96     private void startInstallConfirm() {
97         TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost);
98         tabHost.setup();
99         ViewPager viewPager = (ViewPager)findViewById(R.id.pager);
100         TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);
101 
102         boolean permVisible = false;
103         mScrollView = null;
104         mOkCanInstall = false;
105         int msg = 0;
106         if (mPkgInfo != null) {
107             AppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo);
108             final int NP = perms.getPermissionCount(AppSecurityPermissions.WHICH_PERSONAL);
109             final int ND = perms.getPermissionCount(AppSecurityPermissions.WHICH_DEVICE);
110             if (mAppInfo != null) {
111                 msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
112                         ? R.string.install_confirm_question_update_system
113                         : R.string.install_confirm_question_update;
114                 mScrollView = new CaffeinatedScrollView(this);
115                 mScrollView.setFillViewport(true);
116                 if (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0) {
117                     permVisible = true;
118                     mScrollView.addView(perms.getPermissionsView(
119                             AppSecurityPermissions.WHICH_NEW));
120                 } else {
121                     LayoutInflater inflater = (LayoutInflater)getSystemService(
122                             Context.LAYOUT_INFLATER_SERVICE);
123                     TextView label = (TextView)inflater.inflate(R.layout.label, null);
124                     label.setText(R.string.no_new_perms);
125                     mScrollView.addView(label);
126                 }
127                 adapter.addTab(tabHost.newTabSpec("new").setIndicator(
128                         getText(R.string.newPerms)), mScrollView);
129             } else  {
130                 findViewById(R.id.tabscontainer).setVisibility(View.GONE);
131                 findViewById(R.id.divider).setVisibility(View.VISIBLE);
132             }
133             if (NP > 0 || ND > 0) {
134                 permVisible = true;
135                 LayoutInflater inflater = (LayoutInflater)getSystemService(
136                         Context.LAYOUT_INFLATER_SERVICE);
137                 View root = inflater.inflate(R.layout.permissions_list, null);
138                 if (mScrollView == null) {
139                     mScrollView = (CaffeinatedScrollView)root.findViewById(R.id.scrollview);
140                 }
141                 if (NP > 0) {
142                     ((ViewGroup)root.findViewById(R.id.privacylist)).addView(
143                             perms.getPermissionsView(AppSecurityPermissions.WHICH_PERSONAL));
144                 } else {
145                     root.findViewById(R.id.privacylist).setVisibility(View.GONE);
146                 }
147                 if (ND > 0) {
148                     ((ViewGroup)root.findViewById(R.id.devicelist)).addView(
149                             perms.getPermissionsView(AppSecurityPermissions.WHICH_DEVICE));
150                 } else {
151                     root.findViewById(R.id.devicelist).setVisibility(View.GONE);
152                 }
153                 adapter.addTab(tabHost.newTabSpec("all").setIndicator(
154                         getText(R.string.allPerms)), root);
155             }
156         }
157         if (!permVisible) {
158             if (mAppInfo != null) {
159                 // This is an update to an application, but there are no
160                 // permissions at all.
161                 msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
162                         ? R.string.install_confirm_question_update_system_no_perms
163                         : R.string.install_confirm_question_update_no_perms;
164             } else {
165                 // This is a new application with no permissions.
166                 msg = R.string.install_confirm_question_no_perms;
167             }
168             tabHost.setVisibility(View.GONE);
169             findViewById(R.id.filler).setVisibility(View.VISIBLE);
170             findViewById(R.id.divider).setVisibility(View.GONE);
171             mScrollView = null;
172         }
173         if (msg != 0) {
174             ((TextView)findViewById(R.id.install_confirm_question)).setText(msg);
175         }
176         mInstallConfirm.setVisibility(View.VISIBLE);
177         mOk = (Button)findViewById(R.id.ok_button);
178         mCancel = (Button)findViewById(R.id.cancel_button);
179         mOk.setOnClickListener(this);
180         mCancel.setOnClickListener(this);
181         if (mScrollView == null) {
182             // There is nothing to scroll view, so the ok button is immediately
183             // set to install.
184             mOk.setText(R.string.install);
185             mOkCanInstall = true;
186         } else {
187             mScrollView.setFullScrollAction(new Runnable() {
188                 @Override
189                 public void run() {
190                     mOk.setText(R.string.install);
191                     mOkCanInstall = true;
192                 }
193             });
194         }
195     }
196 
showDialogInner(int id)197     private void showDialogInner(int id) {
198         // TODO better fix for this? Remove dialog so that it gets created again
199         removeDialog(id);
200         showDialog(id);
201     }
202 
203     @Override
onCreateDialog(int id, Bundle bundle)204     public Dialog onCreateDialog(int id, Bundle bundle) {
205         switch (id) {
206         case DLG_UNKNOWN_APPS:
207             return new AlertDialog.Builder(this)
208                     .setTitle(R.string.unknown_apps_dlg_title)
209                     .setMessage(R.string.unknown_apps_dlg_text)
210                     .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
211                         public void onClick(DialogInterface dialog, int which) {
212                             Log.i(TAG, "Finishing off activity so that user can navigate to settings manually");
213                             finish();
214                         }})
215                     .setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() {
216                         public void onClick(DialogInterface dialog, int which) {
217                             Log.i(TAG, "Launching settings");
218                             launchSettingsAppAndFinish();
219                         }
220                     })
221                     .setOnCancelListener(this)
222                     .create();
223         case DLG_PACKAGE_ERROR :
224             return new AlertDialog.Builder(this)
225                     .setTitle(R.string.Parse_error_dlg_title)
226                     .setMessage(R.string.Parse_error_dlg_text)
227                     .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
228                         public void onClick(DialogInterface dialog, int which) {
229                             finish();
230                         }
231                     })
232                     .setOnCancelListener(this)
233                     .create();
234         case DLG_OUT_OF_SPACE:
235             // Guaranteed not to be null. will default to package name if not set by app
236             CharSequence appTitle = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
237             String dlgText = getString(R.string.out_of_space_dlg_text,
238                     appTitle.toString());
239             return new AlertDialog.Builder(this)
240                     .setTitle(R.string.out_of_space_dlg_title)
241                     .setMessage(dlgText)
242                     .setPositiveButton(R.string.manage_applications, new DialogInterface.OnClickListener() {
243                         public void onClick(DialogInterface dialog, int which) {
244                             //launch manage applications
245                             Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
246                             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
247                             startActivity(intent);
248                             finish();
249                         }
250                     })
251                     .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
252                         public void onClick(DialogInterface dialog, int which) {
253                             Log.i(TAG, "Canceling installation");
254                             finish();
255                         }
256                   })
257                   .setOnCancelListener(this)
258                   .create();
259         case DLG_INSTALL_ERROR :
260             // Guaranteed not to be null. will default to package name if not set by app
261             CharSequence appTitle1 = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
262             String dlgText1 = getString(R.string.install_failed_msg,
263                     appTitle1.toString());
264             return new AlertDialog.Builder(this)
265                     .setTitle(R.string.install_failed)
266                     .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
267                         public void onClick(DialogInterface dialog, int which) {
268                             finish();
269                         }
270                     })
271                     .setMessage(dlgText1)
272                     .setOnCancelListener(this)
273                     .create();
274         case DLG_ALLOW_SOURCE:
275             CharSequence appTitle2 = mPm.getApplicationLabel(mSourceInfo);
276             String dlgText2 = getString(R.string.allow_source_dlg_text,
277                     appTitle2.toString());
278             return new AlertDialog.Builder(this)
279                     .setTitle(R.string.allow_source_dlg_title)
280                     .setMessage(dlgText2)
281                     .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
282                         public void onClick(DialogInterface dialog, int which) {
283                             setResult(RESULT_CANCELED);
284                             finish();
285                         }})
286                     .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
287                         public void onClick(DialogInterface dialog, int which) {
288                             SharedPreferences prefs = getSharedPreferences(PREFS_ALLOWED_SOURCES,
289                                     Context.MODE_PRIVATE);
290                             prefs.edit().putBoolean(mSourceInfo.packageName, true).apply();
291                             startInstallConfirm();
292                         }
293                     })
294                     .setOnCancelListener(this)
295                     .create();
296        }
297        return null;
298    }
299 
300     private void launchSettingsAppAndFinish() {
301         // Create an intent to launch SettingsTwo activity
302         Intent launchSettingsIntent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
303         launchSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
304         startActivity(launchSettingsIntent);
305         finish();
306     }
307 
308     private boolean isInstallingUnknownAppsAllowed() {
309         return Settings.Global.getInt(getContentResolver(),
310             Settings.Global.INSTALL_NON_MARKET_APPS, 0) > 0;
311     }
312 
313     private void initiateInstall() {
314         String pkgName = mPkgInfo.packageName;
315         // Check if there is already a package on the device with this name
316         // but it has been renamed to something else.
317         String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
318         if (oldName != null && oldName.length > 0 && oldName[0] != null) {
319             pkgName = oldName[0];
320             mPkgInfo.packageName = pkgName;
321             mPkgInfo.applicationInfo.packageName = pkgName;
322         }
323         // Check if package is already installed. display confirmation dialog if replacing pkg
324         try {
325             // This is a little convoluted because we want to get all uninstalled
326             // apps, but this may include apps with just data, and if it is just
327             // data we still want to count it as "installed".
328             mAppInfo = mPm.getApplicationInfo(pkgName,
329                     PackageManager.GET_UNINSTALLED_PACKAGES);
330             if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
331                 mAppInfo = null;
332             }
333         } catch (NameNotFoundException e) {
334             mAppInfo = null;
335         }
336         startInstallConfirm();
337     }
338 
339     void setPmResult(int pmResult) {
340         Intent result = new Intent();
341         result.putExtra(Intent.EXTRA_INSTALL_RESULT, pmResult);
342         setResult(pmResult == PackageManager.INSTALL_SUCCEEDED
343                 ? RESULT_OK : RESULT_FIRST_USER, result);
344     }
345 
346     @Override
347     protected void onCreate(Bundle icicle) {
348         super.onCreate(icicle);
349 
350         // get intent information
351         final Intent intent = getIntent();
352         mPackageURI = intent.getData();
353         mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
354         mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
355         mPm = getPackageManager();
356 
357         final String scheme = mPackageURI.getScheme();
358         if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
359             Log.w(TAG, "Unsupported scheme " + scheme);
360             setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
361             return;
362         }
363 
364         final PackageUtil.AppSnippet as;
365         if ("package".equals(mPackageURI.getScheme())) {
366             try {
367                 mPkgInfo = mPm.getPackageInfo(mPackageURI.getSchemeSpecificPart(),
368                         PackageManager.GET_PERMISSIONS | PackageManager.GET_UNINSTALLED_PACKAGES);
369             } catch (NameNotFoundException e) {
370             }
371             if (mPkgInfo == null) {
372                 Log.w(TAG, "Requested package " + mPackageURI.getScheme()
373                         + " not available. Discontinuing installation");
374                 showDialogInner(DLG_PACKAGE_ERROR);
375                 setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
376                 return;
377             }
378             as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
379                     mPm.getApplicationIcon(mPkgInfo.applicationInfo));
380         } else {
381             final File sourceFile = new File(mPackageURI.getPath());
382             PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);
383 
384             // Check for parse errors
385             if (parsed == null) {
386                 Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
387                 showDialogInner(DLG_PACKAGE_ERROR);
388                 setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
389                 return;
390             }
391             mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
392                     PackageManager.GET_PERMISSIONS, 0, 0, null,
393                     new PackageUserState());
394             mPkgDigest = parsed.manifestDigest;
395             as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
396         }
397 
398         //set view
399         setContentView(R.layout.install_start);
400         mInstallConfirm = findViewById(R.id.install_confirm_panel);
401         mInstallConfirm.setVisibility(View.INVISIBLE);
402         PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
403 
404         mOriginatingUid = getOriginatingUid(intent);
405 
406         // Deal with install source.
407         String callerPackage = getCallingPackage();
408         if (callerPackage != null && intent.getBooleanExtra(
409                 Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) {
410             try {
411                 mSourceInfo = mPm.getApplicationInfo(callerPackage, 0);
412                 if (mSourceInfo != null) {
413                     if ((mSourceInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
414                         // System apps don't need to be approved.
415                         initiateInstall();
416                         return;
417                     }
418                     /* for now this is disabled, since the user would need to
419                      * have enabled the global "unknown sources" setting in the
420                      * first place in order to get here.
421                     SharedPreferences prefs = getSharedPreferences(PREFS_ALLOWED_SOURCES,
422                             Context.MODE_PRIVATE);
423                     if (prefs.getBoolean(mSourceInfo.packageName, false)) {
424                         // User has already allowed this one.
425                         initiateInstall();
426                         return;
427                     }
428                     //ask user to enable setting first
429                     showDialogInner(DLG_ALLOW_SOURCE);
430                     return;
431                      */
432                 }
433             } catch (NameNotFoundException e) {
434             }
435         }
436 
437         // Check unknown sources.
438         if (!isInstallingUnknownAppsAllowed()) {
439             //ask user to enable setting first
440             showDialogInner(DLG_UNKNOWN_APPS);
441             return;
442         }
443         initiateInstall();
444     }
445 
446     /** Get the ApplicationInfo for the calling package, if available */
447     private ApplicationInfo getSourceInfo() {
448         String callingPackage = getCallingPackage();
449         if (callingPackage != null) {
450             try {
451                 return mPm.getApplicationInfo(callingPackage, 0);
452             } catch (NameNotFoundException ex) {
453                 // ignore
454             }
455         }
456         return null;
457     }
458 
459 
460     /** Get the originating uid if possible, or VerificationParams.NO_UID if not available */
461     private int getOriginatingUid(Intent intent) {
462         // The originating uid from the intent. We only trust/use this if it comes from a
463         // system application
464         int uidFromIntent = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
465                 VerificationParams.NO_UID);
466 
467         // Get the source info from the calling package, if available. This will be the
468         // definitive calling package, but it only works if the intent was started using
469         // startActivityForResult,
470         ApplicationInfo sourceInfo = getSourceInfo();
471         if (sourceInfo != null) {
472             if (uidFromIntent != VerificationParams.NO_UID &&
473                     (mSourceInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
474                 return uidFromIntent;
475 
476             }
477             // We either didn't get a uid in the intent, or we don't trust it. Use the
478             // uid of the calling package instead.
479             return sourceInfo.uid;
480         }
481 
482         // We couldn't get the specific calling package. Let's get the uid instead
483         int callingUid;
484         try {
485             callingUid = ActivityManagerNative.getDefault()
486                     .getLaunchedFromUid(getActivityToken());
487         } catch (android.os.RemoteException ex) {
488             Log.w(TAG, "Could not determine the launching uid.");
489             // nothing else we can do
490             return VerificationParams.NO_UID;
491         }
492 
493         // If we got a uid from the intent, we need to verify that the caller is a
494         // system package before we use it
495         if (uidFromIntent != VerificationParams.NO_UID) {
496             String[] callingPackages = mPm.getPackagesForUid(callingUid);
497             if (callingPackages != null) {
498                 for (String packageName: callingPackages) {
499                     try {
500                         ApplicationInfo applicationInfo =
501                                 mPm.getApplicationInfo(packageName, 0);
502 
503                         if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
504                             return uidFromIntent;
505                         }
506                     } catch (NameNotFoundException ex) {
507                         // ignore it, and try the next package
508                     }
509                 }
510             }
511         }
512         // We either didn't get a uid from the intent, or we don't trust it. Use the
513         // calling uid instead.
514         return callingUid;
515     }
516 
517     // Generic handling when pressing back key
518     public void onCancel(DialogInterface dialog) {
519         finish();
520     }
521 
522     public void onClick(View v) {
523         if(v == mOk) {
524             if (mOkCanInstall || mScrollView == null) {
525                 // Start subactivity to actually install the application
526                 Intent newIntent = new Intent();
527                 newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
528                         mPkgInfo.applicationInfo);
529                 newIntent.setData(mPackageURI);
530                 newIntent.setClass(this, InstallAppProgress.class);
531                 newIntent.putExtra(InstallAppProgress.EXTRA_MANIFEST_DIGEST, mPkgDigest);
532                 String installerPackageName = getIntent().getStringExtra(
533                         Intent.EXTRA_INSTALLER_PACKAGE_NAME);
534                 if (mOriginatingURI != null) {
535                     newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
536                 }
537                 if (mReferrerURI != null) {
538                     newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
539                 }
540                 if (mOriginatingUid != VerificationParams.NO_UID) {
541                     newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
542                 }
543                 if (installerPackageName != null) {
544                     newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
545                             installerPackageName);
546                 }
547                 if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
548                     newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
549                     newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
550                 }
551                 if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
552                 startActivity(newIntent);
553                 finish();
554             } else {
555                 mScrollView.pageScroll(View.FOCUS_DOWN);
556             }
557         } else if(v == mCancel) {
558             // Cancel and finish
559             setResult(RESULT_CANCELED);
560             finish();
561         }
562     }
563 }
564