• 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.pm.ApplicationInfo;
28 import android.content.pm.PackageInfo;
29 import android.content.pm.PackageInstaller;
30 import android.content.pm.PackageManager;
31 import android.content.pm.PackageManager.NameNotFoundException;
32 import android.content.pm.PackageParser;
33 import android.content.pm.PackageUserState;
34 import android.content.pm.VerificationParams;
35 import android.graphics.drawable.Drawable;
36 import android.net.Uri;
37 import android.os.AsyncTask;
38 import android.os.Build;
39 import android.os.Bundle;
40 import android.os.Process;
41 import android.os.UserManager;
42 import android.provider.Settings;
43 import android.support.v4.view.ViewPager;
44 import android.util.Log;
45 import android.view.LayoutInflater;
46 import android.view.View;
47 import android.view.View.OnClickListener;
48 import android.view.ViewGroup;
49 import android.widget.AppSecurityPermissions;
50 import android.widget.Button;
51 import android.widget.ImageView;
52 import android.widget.TabHost;
53 import android.widget.TextView;
54 import com.android.packageinstaller.permission.utils.Utils;
55 
56 import java.io.File;
57 import java.io.FileOutputStream;
58 import java.io.IOException;
59 import java.io.InputStream;
60 import java.io.OutputStream;
61 
62 /*
63  * This activity is launched when a new application is installed via side loading
64  * The package is first parsed and the user is notified of parse errors via a dialog.
65  * If the package is successfully parsed, the user is notified to turn on the install unknown
66  * applications setting. A memory check is made at this point and the user is notified of out
67  * of memory conditions if any. If the package is already existing on the device,
68  * a confirmation dialog (to replace the existing package) is presented to the user.
69  * Based on the user response the package is then installed by launching InstallAppConfirm
70  * sub activity. All state transitions are handled in this activity
71  */
72 public class PackageInstallerActivity extends Activity implements OnCancelListener, OnClickListener {
73     private static final String TAG = "PackageInstaller";
74 
75     private static final int REQUEST_ENABLE_UNKNOWN_SOURCES = 1;
76 
77     private static final String SCHEME_FILE = "file";
78     private static final String SCHEME_CONTENT = "content";
79     private static final String SCHEME_PACKAGE = "package";
80 
81     private int mSessionId = -1;
82     private Uri mPackageURI;
83     private Uri mOriginatingURI;
84     private Uri mReferrerURI;
85     private int mOriginatingUid = VerificationParams.NO_UID;
86     private File mContentUriApkStagingFile;
87 
88     private AsyncTask<Uri, Void, File> mStagingAsynTask;
89 
90     private boolean localLOGV = false;
91     PackageManager mPm;
92     UserManager mUserManager;
93     PackageInstaller mInstaller;
94     PackageInfo mPkgInfo;
95     ApplicationInfo mSourceInfo;
96 
97     // ApplicationInfo object primarily used for already existing applications
98     private ApplicationInfo mAppInfo = null;
99 
100     // View for install progress
101     View mInstallConfirm;
102     // Buttons to indicate user acceptance
103     private Button mOk;
104     private Button mCancel;
105     CaffeinatedScrollView mScrollView = null;
106     private boolean mOkCanInstall = false;
107 
108     static final String PREFS_ALLOWED_SOURCES = "allowed_sources";
109 
110     private static final String TAB_ID_ALL = "all";
111     private static final String TAB_ID_NEW = "new";
112 
113     // Dialog identifiers used in showDialog
114     private static final int DLG_BASE = 0;
115     private static final int DLG_UNKNOWN_SOURCES = DLG_BASE + 1;
116     private static final int DLG_PACKAGE_ERROR = DLG_BASE + 2;
117     private static final int DLG_OUT_OF_SPACE = DLG_BASE + 3;
118     private static final int DLG_INSTALL_ERROR = DLG_BASE + 4;
119     private static final int DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES = DLG_BASE + 6;
120     private static final int DLG_NOT_SUPPORTED_ON_WEAR = DLG_BASE + 7;
121 
startInstallConfirm()122     private void startInstallConfirm() {
123         TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost);
124         tabHost.setup();
125         tabHost.setVisibility(View.VISIBLE);
126         ViewPager viewPager = (ViewPager)findViewById(R.id.pager);
127         TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);
128         // If the app supports runtime permissions the new permissions will
129         // be requested at runtime, hence we do not show them at install.
130         boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion
131                 >= Build.VERSION_CODES.M;
132         boolean permVisible = false;
133         mScrollView = null;
134         mOkCanInstall = false;
135         int msg = 0;
136 
137         AppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo);
138         final int N = perms.getPermissionCount(AppSecurityPermissions.WHICH_ALL);
139         if (mAppInfo != null) {
140             msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
141                     ? R.string.install_confirm_question_update_system
142                     : R.string.install_confirm_question_update;
143             mScrollView = new CaffeinatedScrollView(this);
144             mScrollView.setFillViewport(true);
145             boolean newPermissionsFound = false;
146             if (!supportsRuntimePermissions) {
147                 newPermissionsFound =
148                         (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0);
149                 if (newPermissionsFound) {
150                     permVisible = true;
151                     mScrollView.addView(perms.getPermissionsView(
152                             AppSecurityPermissions.WHICH_NEW));
153                 }
154             }
155             if (!supportsRuntimePermissions && !newPermissionsFound) {
156                 LayoutInflater inflater = (LayoutInflater)getSystemService(
157                         Context.LAYOUT_INFLATER_SERVICE);
158                 TextView label = (TextView)inflater.inflate(R.layout.label, null);
159                 label.setText(R.string.no_new_perms);
160                 mScrollView.addView(label);
161             }
162             adapter.addTab(tabHost.newTabSpec(TAB_ID_NEW).setIndicator(
163                     getText(R.string.newPerms)), mScrollView);
164         } else  {
165             findViewById(R.id.tabscontainer).setVisibility(View.GONE);
166             findViewById(R.id.spacer).setVisibility(View.VISIBLE);
167         }
168         if (!supportsRuntimePermissions && N > 0) {
169             permVisible = true;
170             LayoutInflater inflater = (LayoutInflater)getSystemService(
171                     Context.LAYOUT_INFLATER_SERVICE);
172             View root = inflater.inflate(R.layout.permissions_list, null);
173             if (mScrollView == null) {
174                 mScrollView = (CaffeinatedScrollView)root.findViewById(R.id.scrollview);
175             }
176             ((ViewGroup)root.findViewById(R.id.permission_list)).addView(
177                         perms.getPermissionsView(AppSecurityPermissions.WHICH_ALL));
178             adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator(
179                     getText(R.string.allPerms)), root);
180         }
181         if (!permVisible) {
182             if (mAppInfo != null) {
183                 // This is an update to an application, but there are no
184                 // permissions at all.
185                 msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
186                         ? R.string.install_confirm_question_update_system_no_perms
187                         : R.string.install_confirm_question_update_no_perms;
188 
189                 findViewById(R.id.spacer).setVisibility(View.VISIBLE);
190             } else {
191                 // This is a new application with no permissions.
192                 msg = R.string.install_confirm_question_no_perms;
193             }
194             tabHost.setVisibility(View.INVISIBLE);
195             mScrollView = null;
196         }
197         if (msg != 0) {
198             ((TextView)findViewById(R.id.install_confirm_question)).setText(msg);
199         }
200         mInstallConfirm.setVisibility(View.VISIBLE);
201         mOk.setEnabled(true);
202         if (mScrollView == null) {
203             // There is nothing to scroll view, so the ok button is immediately
204             // set to install.
205             mOk.setText(R.string.install);
206             mOkCanInstall = true;
207         } else {
208             mScrollView.setFullScrollAction(new Runnable() {
209                 @Override
210                 public void run() {
211                     mOk.setText(R.string.install);
212                     mOkCanInstall = true;
213                 }
214             });
215         }
216     }
217 
showDialogInner(int id)218     private void showDialogInner(int id) {
219         // TODO better fix for this? Remove dialog so that it gets created again
220         removeDialog(id);
221         showDialog(id);
222     }
223 
224     @Override
onCreateDialog(int id, Bundle bundle)225     public Dialog onCreateDialog(int id, Bundle bundle) {
226         switch (id) {
227         case DLG_UNKNOWN_SOURCES:
228             return new AlertDialog.Builder(this)
229                     .setMessage(R.string.unknown_apps_dlg_text)
230                     .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
231                         public void onClick(DialogInterface dialog, int which) {
232                             Log.i(TAG, "Finishing off activity so that user can navigate to settings manually");
233                             finishAffinity();
234                         }})
235                     .setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() {
236                         public void onClick(DialogInterface dialog, int which) {
237                             Log.i(TAG, "Launching settings");
238                             launchSecuritySettings();
239                         }
240                     })
241                     .setOnCancelListener(this)
242                     .create();
243         case DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES:
244             return new AlertDialog.Builder(this)
245                     .setMessage(R.string.unknown_apps_admin_dlg_text)
246                     .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
247                         public void onClick(DialogInterface dialog, int which) {
248                             finish();
249                         }
250                     })
251                     .setOnCancelListener(this)
252                     .create();
253         case DLG_PACKAGE_ERROR :
254             return new AlertDialog.Builder(this)
255                     .setMessage(R.string.Parse_error_dlg_text)
256                     .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
257                         public void onClick(DialogInterface dialog, int which) {
258                             finish();
259                         }
260                     })
261                     .setOnCancelListener(this)
262                     .create();
263         case DLG_OUT_OF_SPACE:
264             // Guaranteed not to be null. will default to package name if not set by app
265             CharSequence appTitle = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
266             String dlgText = getString(R.string.out_of_space_dlg_text,
267                     appTitle.toString());
268             return new AlertDialog.Builder(this)
269                     .setMessage(dlgText)
270                     .setPositiveButton(R.string.manage_applications, new DialogInterface.OnClickListener() {
271                         public void onClick(DialogInterface dialog, int which) {
272                             //launch manage applications
273                             Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
274                             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
275                             startActivity(intent);
276                             finish();
277                         }
278                     })
279                     .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
280                         public void onClick(DialogInterface dialog, int which) {
281                             Log.i(TAG, "Canceling installation");
282                             finish();
283                         }
284                 })
285                   .setOnCancelListener(this)
286                   .create();
287         case DLG_INSTALL_ERROR :
288             // Guaranteed not to be null. will default to package name if not set by app
289             CharSequence appTitle1 = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
290             String dlgText1 = getString(R.string.install_failed_msg,
291                     appTitle1.toString());
292             return new AlertDialog.Builder(this)
293                     .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
294                         public void onClick(DialogInterface dialog, int which) {
295                             finish();
296                         }
297                     })
298                     .setMessage(dlgText1)
299                     .setOnCancelListener(this)
300                     .create();
301         case DLG_NOT_SUPPORTED_ON_WEAR:
302             return new AlertDialog.Builder(this)
303                     .setMessage(R.string.wear_not_allowed_dlg_text)
304                     .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
305                         public void onClick(DialogInterface dialog, int which) {
306                             setResult(RESULT_OK);
307                             clearCachedApkIfNeededAndFinish();
308                         }
309                     })
310                     .setOnCancelListener(this)
311                     .create();
312        }
313        return null;
314     }
315 
316     private void launchSecuritySettings() {
317         Intent launchSettingsIntent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
318         startActivityForResult(launchSettingsIntent, REQUEST_ENABLE_UNKNOWN_SOURCES);
319     }
320 
321     @Override
322     public void onActivityResult(int request, int result, Intent data) {
323         // If the settings app approved the install we are good to go regardless
324         // whether the untrusted sources setting is on. This allows partners to
325         // implement a "allow untrusted source once" feature.
326         if (request == REQUEST_ENABLE_UNKNOWN_SOURCES && result == RESULT_OK) {
327             initiateInstall();
328         } else {
329             clearCachedApkIfNeededAndFinish();
330         }
331     }
332 
333     private boolean isInstallRequestFromUnknownSource(Intent intent) {
334         String callerPackage = getCallingPackage();
335         if (callerPackage != null && intent.getBooleanExtra(
336                 Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) {
337             try {
338                 mSourceInfo = mPm.getApplicationInfo(callerPackage, 0);
339                 if (mSourceInfo != null) {
340                     if ((mSourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
341                             != 0) {
342                         // Privileged apps are not considered an unknown source.
343                         return false;
344                     }
345                 }
346             } catch (NameNotFoundException e) {
347             }
348         }
349 
350         return true;
351     }
352 
353     /**
354      * @return whether unknown sources is enabled by user in Settings
355      */
356     private boolean isUnknownSourcesEnabled() {
357         return Settings.Secure.getInt(getContentResolver(),
358                 Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 0;
359     }
360 
361     /**
362      * @return whether the device admin restricts installation from unknown sources
363      */
364     private boolean isUnknownSourcesDisallowed() {
365         return mUserManager.hasUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
366     }
367 
368     private void initiateInstall() {
369         String pkgName = mPkgInfo.packageName;
370         // Check if there is already a package on the device with this name
371         // but it has been renamed to something else.
372         String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
373         if (oldName != null && oldName.length > 0 && oldName[0] != null) {
374             pkgName = oldName[0];
375             mPkgInfo.packageName = pkgName;
376             mPkgInfo.applicationInfo.packageName = pkgName;
377         }
378         // Check if package is already installed. display confirmation dialog if replacing pkg
379         try {
380             // This is a little convoluted because we want to get all uninstalled
381             // apps, but this may include apps with just data, and if it is just
382             // data we still want to count it as "installed".
383             mAppInfo = mPm.getApplicationInfo(pkgName,
384                     PackageManager.GET_UNINSTALLED_PACKAGES);
385             if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
386                 mAppInfo = null;
387             }
388         } catch (NameNotFoundException e) {
389             mAppInfo = null;
390         }
391 
392         startInstallConfirm();
393     }
394 
395     void setPmResult(int pmResult) {
396         Intent result = new Intent();
397         result.putExtra(Intent.EXTRA_INSTALL_RESULT, pmResult);
398         setResult(pmResult == PackageManager.INSTALL_SUCCEEDED
399                 ? RESULT_OK : RESULT_FIRST_USER, result);
400     }
401 
402     @Override
403     protected void onCreate(Bundle icicle) {
404         super.onCreate(icicle);
405 
406         mPm = getPackageManager();
407         mInstaller = mPm.getPackageInstaller();
408         mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
409 
410         final Intent intent = getIntent();
411         mOriginatingUid = getOriginatingUid(intent);
412 
413         final Uri packageUri;
414 
415         if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
416             final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
417             final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
418             if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
419                 Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
420                 finish();
421                 return;
422             }
423 
424             mSessionId = sessionId;
425             packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
426             mOriginatingURI = null;
427             mReferrerURI = null;
428         } else {
429             mSessionId = -1;
430             packageUri = intent.getData();
431             mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
432             mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
433         }
434 
435         // if there's nothing to do, quietly slip into the ether
436         if (packageUri == null) {
437             Log.w(TAG, "Unspecified source");
438             setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
439             finish();
440             return;
441         }
442 
443         if (DeviceUtils.isWear(this)) {
444             showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
445             return;
446         }
447 
448         //set view
449         setContentView(R.layout.install_start);
450         mInstallConfirm = findViewById(R.id.install_confirm_panel);
451         mInstallConfirm.setVisibility(View.INVISIBLE);
452         mOk = (Button)findViewById(R.id.ok_button);
453         mCancel = (Button)findViewById(R.id.cancel_button);
454         mOk.setOnClickListener(this);
455         mCancel.setOnClickListener(this);
456 
457         // Block the install attempt on the Unknown Sources setting if necessary.
458         final boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent);
459         if (!requestFromUnknownSource) {
460             processPackageUri(packageUri);
461             return;
462         }
463 
464         // If the admin prohibits it, or we're running in a managed profile, just show error
465         // and exit. Otherwise show an option to take the user to Settings to change the setting.
466         final boolean isManagedProfile = mUserManager.isManagedProfile();
467         if (isUnknownSourcesDisallowed()) {
468             if ((mUserManager.getUserRestrictionSource(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
469                     Process.myUserHandle()) & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
470                 showDialogInner(DLG_UNKNOWN_SOURCES);
471             } else {
472                 startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
473                 clearCachedApkIfNeededAndFinish();
474             }
475         } else if (!isUnknownSourcesEnabled() && isManagedProfile) {
476             showDialogInner(DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES);
477         } else if (!isUnknownSourcesEnabled()) {
478             // Ask user to enable setting first
479 
480             showDialogInner(DLG_UNKNOWN_SOURCES);
481         } else {
482             processPackageUri(packageUri);
483         }
484     }
485 
486     @Override
487     protected void onDestroy() {
488         if (mStagingAsynTask != null) {
489             mStagingAsynTask.cancel(true);
490             mStagingAsynTask = null;
491         }
492         super.onDestroy();
493     }
494 
495     private void processPackageUri(final Uri packageUri) {
496         mPackageURI = packageUri;
497 
498         final String scheme = packageUri.getScheme();
499         final PackageUtil.AppSnippet as;
500 
501         switch (scheme) {
502             case SCHEME_PACKAGE: {
503                 try {
504                     mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
505                             PackageManager.GET_PERMISSIONS
506                                     | PackageManager.GET_UNINSTALLED_PACKAGES);
507                 } catch (NameNotFoundException e) {
508                 }
509                 if (mPkgInfo == null) {
510                     Log.w(TAG, "Requested package " + packageUri.getScheme()
511                             + " not available. Discontinuing installation");
512                     showDialogInner(DLG_PACKAGE_ERROR);
513                     setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
514                     return;
515                 }
516                 as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
517                         mPm.getApplicationIcon(mPkgInfo.applicationInfo));
518             } break;
519 
520             case SCHEME_FILE: {
521                 File sourceFile = new File(packageUri.getPath());
522                 PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);
523 
524                 // Check for parse errors
525                 if (parsed == null) {
526                     Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
527                     showDialogInner(DLG_PACKAGE_ERROR);
528                     setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
529                     return;
530                 }
531                 mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
532                         PackageManager.GET_PERMISSIONS, 0, 0, null,
533                         new PackageUserState());
534                 as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
535             } break;
536 
537             case SCHEME_CONTENT: {
538                 mStagingAsynTask = new StagingAsyncTask();
539                 mStagingAsynTask.execute(packageUri);
540                 return;
541             }
542 
543             default: {
544                 Log.w(TAG, "Unsupported scheme " + scheme);
545                 setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
546                 clearCachedApkIfNeededAndFinish();
547                 return;
548             }
549         }
550 
551         PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
552 
553         initiateInstall();
554     }
555 
556     /** Get the ApplicationInfo for the calling package, if available */
557     private ApplicationInfo getSourceInfo() {
558         String callingPackage = getCallingPackage();
559         if (callingPackage != null) {
560             try {
561                 return mPm.getApplicationInfo(callingPackage, 0);
562             } catch (NameNotFoundException ex) {
563                 // ignore
564             }
565         }
566         return null;
567     }
568 
569 
570     /** Get the originating uid if possible, or VerificationParams.NO_UID if not available */
571     private int getOriginatingUid(Intent intent) {
572         // The originating uid from the intent. We only trust/use this if it comes from a
573         // system application
574         int uidFromIntent = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
575                 VerificationParams.NO_UID);
576 
577         // Get the source info from the calling package, if available. This will be the
578         // definitive calling package, but it only works if the intent was started using
579         // startActivityForResult,
580         ApplicationInfo sourceInfo = getSourceInfo();
581         if (sourceInfo != null) {
582             if (uidFromIntent != VerificationParams.NO_UID &&
583                     (mSourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
584                 return uidFromIntent;
585 
586             }
587             // We either didn't get a uid in the intent, or we don't trust it. Use the
588             // uid of the calling package instead.
589             return sourceInfo.uid;
590         }
591 
592         // We couldn't get the specific calling package. Let's get the uid instead
593         int callingUid;
594         try {
595             callingUid = ActivityManagerNative.getDefault()
596                     .getLaunchedFromUid(getActivityToken());
597         } catch (android.os.RemoteException ex) {
598             Log.w(TAG, "Could not determine the launching uid.");
599             // nothing else we can do
600             return VerificationParams.NO_UID;
601         }
602 
603         // If we got a uid from the intent, we need to verify that the caller is a
604         // privileged system package before we use it
605         if (uidFromIntent != VerificationParams.NO_UID) {
606             String[] callingPackages = mPm.getPackagesForUid(callingUid);
607             if (callingPackages != null) {
608                 for (String packageName: callingPackages) {
609                     try {
610                         ApplicationInfo applicationInfo =
611                                 mPm.getApplicationInfo(packageName, 0);
612 
613                         if ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
614                                 != 0) {
615                             return uidFromIntent;
616                         }
617                     } catch (NameNotFoundException ex) {
618                         // ignore it, and try the next package
619                     }
620                 }
621             }
622         }
623         // We either didn't get a uid from the intent, or we don't trust it. Use the
624         // calling uid instead.
625         return callingUid;
626     }
627 
628     @Override
629     public void onBackPressed() {
630         if (mSessionId != -1) {
631             mInstaller.setPermissionsResult(mSessionId, false);
632         }
633         super.onBackPressed();
634     }
635 
636     // Generic handling when pressing back key
637     public void onCancel(DialogInterface dialog) {
638         clearCachedApkIfNeededAndFinish();
639     }
640 
641     public void onClick(View v) {
642         if (v == mOk) {
643             if (mOkCanInstall || mScrollView == null) {
644                 if (mSessionId != -1) {
645                     mInstaller.setPermissionsResult(mSessionId, true);
646                     clearCachedApkIfNeededAndFinish();
647                 } else {
648                     startInstall();
649                 }
650             } else {
651                 mScrollView.pageScroll(View.FOCUS_DOWN);
652             }
653         } else if (v == mCancel) {
654             // Cancel and finish
655             setResult(RESULT_CANCELED);
656             if (mSessionId != -1) {
657                 mInstaller.setPermissionsResult(mSessionId, false);
658             }
659             clearCachedApkIfNeededAndFinish();
660         }
661     }
662 
663     private void startInstall() {
664         // Start subactivity to actually install the application
665         Intent newIntent = new Intent();
666         newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
667                 mPkgInfo.applicationInfo);
668         newIntent.setData(mPackageURI);
669         newIntent.setClass(this, InstallAppProgress.class);
670         String installerPackageName = getIntent().getStringExtra(
671                 Intent.EXTRA_INSTALLER_PACKAGE_NAME);
672         if (mOriginatingURI != null) {
673             newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
674         }
675         if (mReferrerURI != null) {
676             newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
677         }
678         if (mOriginatingUid != VerificationParams.NO_UID) {
679             newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
680         }
681         if (installerPackageName != null) {
682             newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
683                     installerPackageName);
684         }
685         if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
686             newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
687             newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
688         }
689         if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
690         startActivity(newIntent);
691         finish();
692     }
693 
694     private void clearCachedApkIfNeededAndFinish() {
695         if (mContentUriApkStagingFile != null) {
696             mContentUriApkStagingFile.delete();
697             mContentUriApkStagingFile = null;
698         }
699         finish();
700     }
701 
702     private final class StagingAsyncTask extends AsyncTask<Uri, Void, File> {
703         private static final long SHOW_EMPTY_STATE_DELAY_MILLIS = 300;
704 
705         private final Runnable mEmptyStateRunnable = new Runnable() {
706             @Override
707             public void run() {
708                 ((TextView) findViewById(R.id.app_name)).setText(R.string.app_name_unknown);
709                 ((TextView) findViewById(R.id.install_confirm_question))
710                         .setText(R.string.message_staging);
711                 mInstallConfirm.setVisibility(View.VISIBLE);
712                 findViewById(android.R.id.tabhost).setVisibility(View.INVISIBLE);
713                 findViewById(R.id.spacer).setVisibility(View.VISIBLE);
714                 findViewById(R.id.ok_button).setEnabled(false);
715                 Drawable icon = getDrawable(R.drawable.ic_file_download);
716                 Utils.applyTint(PackageInstallerActivity.this,
717                         icon, android.R.attr.colorControlNormal);
718                 ((ImageView) findViewById(R.id.app_icon)).setImageDrawable(icon);
719             }
720         };
721 
722         @Override
723         protected void onPreExecute() {
724             getWindow().getDecorView().postDelayed(mEmptyStateRunnable,
725                     SHOW_EMPTY_STATE_DELAY_MILLIS);
726         }
727 
728         @Override
729         protected File doInBackground(Uri... params) {
730             if (params == null || params.length <= 0) {
731                 return null;
732             }
733             Uri packageUri = params[0];
734             File sourceFile = null;
735             try {
736                 sourceFile = File.createTempFile("package", ".apk", getCacheDir());
737                 try (
738                     InputStream in = getContentResolver().openInputStream(packageUri);
739                     OutputStream out = (in != null) ? new FileOutputStream(
740                             sourceFile) : null;
741                 ) {
742                     // Despite the comments in ContentResolver#openInputStream
743                     // the returned stream can be null.
744                     if (in == null) {
745                         return null;
746                     }
747                     byte[] buffer = new byte[4096];
748                     int bytesRead;
749                     while ((bytesRead = in.read(buffer)) >= 0) {
750                         // Be nice and respond to a cancellation
751                         if (isCancelled()) {
752                             return null;
753                         }
754                         out.write(buffer, 0, bytesRead);
755                     }
756                 }
757             } catch (IOException ioe) {
758                 Log.w(TAG, "Error staging apk from content URI", ioe);
759                 if (sourceFile != null) {
760                     sourceFile.delete();
761                 }
762             }
763             return sourceFile;
764         }
765 
766         @Override
767         protected void onPostExecute(File file) {
768             getWindow().getDecorView().removeCallbacks(mEmptyStateRunnable);
769             if (isFinishing() || isDestroyed()) {
770                 return;
771             }
772             if (file == null) {
773                 showDialogInner(DLG_PACKAGE_ERROR);
774                 setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
775                 return;
776             }
777             mContentUriApkStagingFile = file;
778             Uri fileUri = Uri.fromFile(file);
779             processPackageUri(fileUri);
780         }
781 
782         @Override
783         protected void onCancelled(File file) {
784             getWindow().getDecorView().removeCallbacks(mEmptyStateRunnable);
785         }
786     };
787 }
788