• 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.AlertDialog;
21 import android.app.Dialog;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.DialogInterface.OnCancelListener;
25 import android.content.Intent;
26 import android.content.SharedPreferences;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageManager;
29 import android.content.pm.PackageManager.NameNotFoundException;
30 import android.content.pm.PackageParser;
31 import android.graphics.Rect;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.provider.Settings;
35 import android.util.Log;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.view.View.OnClickListener;
39 import android.view.ViewGroup;
40 import android.widget.AppSecurityPermissions;
41 import android.widget.Button;
42 import android.widget.LinearLayout;
43 import android.widget.ScrollView;
44 import android.widget.TabHost;
45 import android.widget.TabWidget;
46 import android.widget.TextView;
47 
48 import java.io.File;
49 import java.util.ArrayList;
50 
51 /*
52  * This activity is launched when a new application is installed via side loading
53  * The package is first parsed and the user is notified of parse errors via a dialog.
54  * If the package is successfully parsed, the user is notified to turn on the install unknown
55  * applications setting. A memory check is made at this point and the user is notified of out
56  * of memory conditions if any. If the package is already existing on the device,
57  * a confirmation dialog (to replace the existing package) is presented to the user.
58  * Based on the user response the package is then installed by launching InstallAppConfirm
59  * sub activity. All state transitions are handled in this activity
60  */
61 public class PackageInstallerActivity extends Activity implements OnCancelListener, OnClickListener {
62     private static final String TAG = "PackageInstaller";
63     private Uri mPackageURI;
64     private boolean localLOGV = false;
65     PackageManager mPm;
66     PackageParser.Package mPkgInfo;
67     ApplicationInfo mSourceInfo;
68 
69     // ApplicationInfo object primarily used for already existing applications
70     private ApplicationInfo mAppInfo = null;
71 
72     // View for install progress
73     View mInstallConfirm;
74     // Buttons to indicate user acceptance
75     private Button mOk;
76     private Button mCancel;
77 
78     static final String PREFS_ALLOWED_SOURCES = "allowed_sources";
79 
80     // Dialog identifiers used in showDialog
81     private static final int DLG_BASE = 0;
82     private static final int DLG_REPLACE_APP = DLG_BASE + 1;
83     private static final int DLG_UNKNOWN_APPS = DLG_BASE + 2;
84     private static final int DLG_PACKAGE_ERROR = DLG_BASE + 3;
85     private static final int DLG_OUT_OF_SPACE = DLG_BASE + 4;
86     private static final int DLG_INSTALL_ERROR = DLG_BASE + 5;
87     private static final int DLG_ALLOW_SOURCE = DLG_BASE + 6;
88 
startInstallConfirm()89     private void startInstallConfirm() {
90         LinearLayout permsSection = (LinearLayout) mInstallConfirm.findViewById(R.id.permissions_section);
91         LinearLayout securityList = (LinearLayout) permsSection.findViewById(
92                 R.id.security_settings_list);
93         boolean permVisible = false;
94         if(mPkgInfo != null) {
95             AppSecurityPermissions asp = new AppSecurityPermissions(this, mPkgInfo);
96             if(asp.getPermissionCount() > 0) {
97                 permVisible = true;
98                 securityList.addView(asp.getPermissionsView());
99             }
100         }
101         if(!permVisible){
102             permsSection.setVisibility(View.INVISIBLE);
103         }
104         mInstallConfirm.setVisibility(View.VISIBLE);
105         mOk = (Button)findViewById(R.id.ok_button);
106         mCancel = (Button)findViewById(R.id.cancel_button);
107         mOk.setOnClickListener(this);
108         mCancel.setOnClickListener(this);
109     }
110 
showDialogInner(int id)111     private void showDialogInner(int id) {
112         // TODO better fix for this? Remove dialog so that it gets created again
113         removeDialog(id);
114         showDialog(id);
115     }
116 
117     @Override
onCreateDialog(int id, Bundle bundle)118     public Dialog onCreateDialog(int id, Bundle bundle) {
119         switch (id) {
120         case DLG_REPLACE_APP:
121             int msgId = R.string.dlg_app_replacement_statement;
122             // Customized text for system apps
123             if ((mAppInfo != null) && (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
124                 msgId = R.string.dlg_sys_app_replacement_statement;
125             }
126             return new AlertDialog.Builder(this)
127                     .setTitle(R.string.dlg_app_replacement_title)
128                     .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
129                         public void onClick(DialogInterface dialog, int which) {
130                             startInstallConfirm();
131                         }})
132                     .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
133                         public void onClick(DialogInterface dialog, int which) {
134                             Log.i(TAG, "Canceling installation");
135                             setResult(RESULT_CANCELED);
136                             finish();
137                         }})
138                     .setMessage(msgId)
139                     .setOnCancelListener(this)
140                     .create();
141         case DLG_UNKNOWN_APPS:
142             return new AlertDialog.Builder(this)
143                     .setTitle(R.string.unknown_apps_dlg_title)
144                     .setMessage(R.string.unknown_apps_dlg_text)
145                     .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
146                         public void onClick(DialogInterface dialog, int which) {
147                             Log.i(TAG, "Finishing off activity so that user can navigate to settings manually");
148                             finish();
149                         }})
150                     .setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() {
151                         public void onClick(DialogInterface dialog, int which) {
152                             Log.i(TAG, "Launching settings");
153                             launchSettingsAppAndFinish();
154                         }
155                     })
156                     .setOnCancelListener(this)
157                     .create();
158         case DLG_PACKAGE_ERROR :
159             return new AlertDialog.Builder(this)
160                     .setTitle(R.string.Parse_error_dlg_title)
161                     .setMessage(R.string.Parse_error_dlg_text)
162                     .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
163                         public void onClick(DialogInterface dialog, int which) {
164                             finish();
165                         }
166                     })
167                     .setOnCancelListener(this)
168                     .create();
169         case DLG_OUT_OF_SPACE:
170             // Guaranteed not to be null. will default to package name if not set by app
171             CharSequence appTitle = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
172             String dlgText = getString(R.string.out_of_space_dlg_text,
173                     appTitle.toString());
174             return new AlertDialog.Builder(this)
175                     .setTitle(R.string.out_of_space_dlg_title)
176                     .setMessage(dlgText)
177                     .setPositiveButton(R.string.manage_applications, new DialogInterface.OnClickListener() {
178                         public void onClick(DialogInterface dialog, int which) {
179                             //launch manage applications
180                             Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
181                             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
182                             startActivity(intent);
183                             finish();
184                         }
185                     })
186                     .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
187                         public void onClick(DialogInterface dialog, int which) {
188                             Log.i(TAG, "Canceling installation");
189                             finish();
190                         }
191                   })
192                   .setOnCancelListener(this)
193                   .create();
194         case DLG_INSTALL_ERROR :
195             // Guaranteed not to be null. will default to package name if not set by app
196             CharSequence appTitle1 = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
197             String dlgText1 = getString(R.string.install_failed_msg,
198                     appTitle1.toString());
199             return new AlertDialog.Builder(this)
200                     .setTitle(R.string.install_failed)
201                     .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
202                         public void onClick(DialogInterface dialog, int which) {
203                             finish();
204                         }
205                     })
206                     .setMessage(dlgText1)
207                     .setOnCancelListener(this)
208                     .create();
209         case DLG_ALLOW_SOURCE:
210             CharSequence appTitle2 = mPm.getApplicationLabel(mSourceInfo);
211             String dlgText2 = getString(R.string.allow_source_dlg_text,
212                     appTitle2.toString());
213             return new AlertDialog.Builder(this)
214                     .setTitle(R.string.allow_source_dlg_title)
215                     .setMessage(dlgText2)
216                     .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
217                         public void onClick(DialogInterface dialog, int which) {
218                             setResult(RESULT_CANCELED);
219                             finish();
220                         }})
221                     .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
222                         public void onClick(DialogInterface dialog, int which) {
223                             SharedPreferences prefs = getSharedPreferences(PREFS_ALLOWED_SOURCES,
224                                     Context.MODE_PRIVATE);
225                             prefs.edit().putBoolean(mSourceInfo.packageName, true).apply();
226                             startInstallConfirm();
227                         }
228                     })
229                     .setOnCancelListener(this)
230                     .create();
231        }
232        return null;
233    }
234 
235     private void launchSettingsAppAndFinish() {
236         // Create an intent to launch SettingsTwo activity
237         Intent launchSettingsIntent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
238         launchSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
239         startActivity(launchSettingsIntent);
240         finish();
241     }
242 
243     private boolean isInstallingUnknownAppsAllowed() {
244         return Settings.Secure.getInt(getContentResolver(),
245             Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 0;
246     }
247 
248     private void initiateInstall() {
249         String pkgName = mPkgInfo.packageName;
250         // Check if there is already a package on the device with this name
251         // but it has been renamed to something else.
252         String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
253         if (oldName != null && oldName.length > 0 && oldName[0] != null) {
254             pkgName = oldName[0];
255             mPkgInfo.setPackageName(pkgName);
256         }
257         // Check if package is already installed. display confirmation dialog if replacing pkg
258         try {
259             mAppInfo = mPm.getApplicationInfo(pkgName,
260                     PackageManager.GET_UNINSTALLED_PACKAGES);
261         } catch (NameNotFoundException e) {
262             mAppInfo = null;
263         }
264         if (mAppInfo == null || getIntent().getBooleanExtra(Intent.EXTRA_ALLOW_REPLACE, false)) {
265             startInstallConfirm();
266         } else {
267             if(localLOGV) Log.i(TAG, "Replacing existing package:"+
268                     mPkgInfo.applicationInfo.packageName);
269             showDialogInner(DLG_REPLACE_APP);
270         }
271     }
272 
273     void setPmResult(int pmResult) {
274         Intent result = new Intent();
275         result.putExtra(Intent.EXTRA_INSTALL_RESULT, pmResult);
276         setResult(pmResult == PackageManager.INSTALL_SUCCEEDED
277                 ? RESULT_OK : RESULT_FIRST_USER, result);
278     }
279 
280     @Override
281     protected void onCreate(Bundle icicle) {
282         super.onCreate(icicle);
283 
284         // get intent information
285         final Intent intent = getIntent();
286         mPackageURI = intent.getData();
287         mPm = getPackageManager();
288 
289         final String scheme = mPackageURI.getScheme();
290         if (scheme != null && !"file".equals(scheme)) {
291             throw new IllegalArgumentException("unexpected scheme " + scheme);
292         }
293 
294         final File sourceFile = new File(mPackageURI.getPath());
295         mPkgInfo = PackageUtil.getPackageInfo(sourceFile);
296 
297         // Check for parse errors
298         if (mPkgInfo == null) {
299             Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
300             showDialogInner(DLG_PACKAGE_ERROR);
301             setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
302             return;
303         }
304 
305         //set view
306         setContentView(R.layout.install_start);
307         mInstallConfirm = findViewById(R.id.install_confirm_panel);
308         mInstallConfirm.setVisibility(View.INVISIBLE);
309         final PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(
310                 this, mPkgInfo.applicationInfo, sourceFile);
311         PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
312 
313         // Deal with install source.
314         String callerPackage = getCallingPackage();
315         if (callerPackage != null && intent.getBooleanExtra(
316                 Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) {
317             try {
318                 mSourceInfo = mPm.getApplicationInfo(callerPackage, 0);
319                 if (mSourceInfo != null) {
320                     if ((mSourceInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
321                         // System apps don't need to be approved.
322                         initiateInstall();
323                         return;
324                     }
325                     /* for now this is disabled, since the user would need to
326                      * have enabled the global "unknown sources" setting in the
327                      * first place in order to get here.
328                     SharedPreferences prefs = getSharedPreferences(PREFS_ALLOWED_SOURCES,
329                             Context.MODE_PRIVATE);
330                     if (prefs.getBoolean(mSourceInfo.packageName, false)) {
331                         // User has already allowed this one.
332                         initiateInstall();
333                         return;
334                     }
335                     //ask user to enable setting first
336                     showDialogInner(DLG_ALLOW_SOURCE);
337                     return;
338                      */
339                 }
340             } catch (NameNotFoundException e) {
341             }
342         }
343 
344         // Check unknown sources.
345         if (!isInstallingUnknownAppsAllowed()) {
346             //ask user to enable setting first
347             showDialogInner(DLG_UNKNOWN_APPS);
348             return;
349         }
350         initiateInstall();
351     }
352 
353     // Generic handling when pressing back key
354     public void onCancel(DialogInterface dialog) {
355         finish();
356     }
357 
358     public void onClick(View v) {
359         if(v == mOk) {
360             // Start subactivity to actually install the application
361             Intent newIntent = new Intent();
362             newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
363                     mPkgInfo.applicationInfo);
364             newIntent.setData(mPackageURI);
365             newIntent.setClass(this, InstallAppProgress.class);
366             String installerPackageName = getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
367             if (installerPackageName != null) {
368                 newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName);
369             }
370             if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
371                 newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
372                 newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
373             }
374             if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
375             startActivity(newIntent);
376             finish();
377         } else if(v == mCancel) {
378             // Cancel and finish
379             setResult(RESULT_CANCELED);
380             finish();
381         }
382     }
383 
384     /**
385      * It's a ScrollView that knows how to stay awake.
386      */
387     static class CaffeinatedScrollView extends ScrollView {
388         public CaffeinatedScrollView(Context context) {
389             super(context);
390         }
391 
392         /**
393          * Make this visible so we can call it
394          */
395         @Override
396         public boolean awakenScrollBars() {
397             return super.awakenScrollBars();
398         }
399     }
400 }
401