• 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 com.android.packageinstaller.R;
20 
21 import java.io.File;
22 import android.app.Activity;
23 import android.app.AlertDialog;
24 import android.app.Dialog;
25 import android.app.PendingIntent;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.DialogInterface.OnCancelListener;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.PackageManager;
34 import android.content.pm.PackageParser;
35 import android.content.pm.PackageManager.NameNotFoundException;
36 import android.net.Uri;
37 import android.os.Bundle;
38 import android.os.Handler;
39 import android.os.Message;
40 import android.provider.Settings;
41 import android.util.Log;
42 import android.view.View;
43 import android.view.Window;
44 import android.view.View.OnClickListener;
45 import android.widget.AppSecurityPermissions;
46 import android.widget.Button;
47 import android.widget.LinearLayout;
48 
49 /*
50  * This activity is launched when a new application is installed via side loading
51  * The package is first parsed and the user is notified of parse errors via a dialog.
52  * If the package is successfully parsed, the user is notified to turn on the install unknown
53  * applications setting. A memory check is made at this point and the user is notified of out
54  * of memory conditions if any. If the package is already existing on the device,
55  * a confirmation dialog (to replace the existing package) is presented to the user.
56  * Based on the user response the package is then installed by launching InstallAppConfirm
57  * sub activity. All state transitions are handled in this activity
58  */
59 public class PackageInstallerActivity extends Activity implements OnCancelListener, OnClickListener {
60     private static final String TAG = "PackageInstaller";
61     private Uri mPackageURI;
62     private boolean localLOGV = false;
63     PackageManager mPm;
64     private boolean mReplacing = false;
65     private PackageParser.Package mPkgInfo;
66     private static final int SUCCEEDED = 1;
67     private static final int FAILED = 0;
68     // Broadcast receiver for clearing cache
69     ClearCacheReceiver mClearCacheReceiver = null;
70     private static final int HANDLER_BASE_MSG_IDX = 0;
71     private static final int FREE_SPACE = HANDLER_BASE_MSG_IDX + 1;
72 
73     // ApplicationInfo object primarily used for already existing applications
74     private ApplicationInfo mAppInfo = null;
75 
76     // View for install progress
77     View mInstallConfirm;
78     // Buttons to indicate user acceptance
79     private Button mOk;
80     private Button mCancel;
81 
82     // Dialog identifiers used in showDialog
83     private static final int DLG_BASE = 0;
84     private static final int DLG_REPLACE_APP = DLG_BASE + 1;
85     private static final int DLG_UNKNOWN_APPS = DLG_BASE + 2;
86     private static final int DLG_PACKAGE_ERROR = DLG_BASE + 3;
87     private static final int DLG_OUT_OF_SPACE = DLG_BASE + 4;
88     private static final int DLG_INSTALL_ERROR = DLG_BASE + 5;
89 
90     private Handler mHandler = new Handler() {
91         public void handleMessage(Message msg) {
92             switch (msg.what) {
93                 case FREE_SPACE:
94                     if (mClearCacheReceiver != null) {
95                         unregisterReceiver(mClearCacheReceiver);
96                     }
97                     if(msg.arg1 == SUCCEEDED) {
98                         makeTempCopyAndInstall();
99                     } else {
100                         showDialogInner(DLG_OUT_OF_SPACE);
101                     }
102                     break;
103                 default:
104                     break;
105             }
106         }
107     };
108 
startInstallConfirm()109     private void startInstallConfirm() {
110         LinearLayout permsSection = (LinearLayout) mInstallConfirm.findViewById(R.id.permissions_section);
111         LinearLayout securityList = (LinearLayout) permsSection.findViewById(
112                 R.id.security_settings_list);
113         boolean permVisible = false;
114         if(mPkgInfo != null) {
115             AppSecurityPermissions asp = new AppSecurityPermissions(this, mPkgInfo);
116             if(asp.getPermissionCount() > 0) {
117                 permVisible = true;
118                 securityList.addView(asp.getPermissionsView());
119             }
120         }
121         if(!permVisible){
122             permsSection.setVisibility(View.INVISIBLE);
123         }
124         mInstallConfirm.setVisibility(View.VISIBLE);
125         mOk = (Button)findViewById(R.id.ok_button);
126         mCancel = (Button)findViewById(R.id.cancel_button);
127         mOk.setOnClickListener(this);
128         mCancel.setOnClickListener(this);
129     }
130 
showDialogInner(int id)131     private void showDialogInner(int id) {
132         // TODO better fix for this? Remove dialog so that it gets created again
133         removeDialog(id);
134         showDialog(id);
135     }
136 
137     @Override
onCreateDialog(int id)138     public Dialog onCreateDialog(int id) {
139         switch (id) {
140         case DLG_REPLACE_APP:
141             int msgId = R.string.dlg_app_replacement_statement;
142             // Customized text for system apps
143             if ((mAppInfo != null) && (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
144                 msgId = R.string.dlg_sys_app_replacement_statement;
145             }
146             return new AlertDialog.Builder(this)
147                     .setTitle(R.string.dlg_app_replacement_title)
148                     .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
149                         public void onClick(DialogInterface dialog, int which) {
150                             startInstallConfirm();
151                             mReplacing = true;
152                         }})
153                     .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
154                         public void onClick(DialogInterface dialog, int which) {
155                             Log.i(TAG, "Canceling installation");
156                             finish();
157                         }})
158                     .setMessage(msgId)
159                     .setOnCancelListener(this)
160                     .create();
161         case DLG_UNKNOWN_APPS:
162             return new AlertDialog.Builder(this)
163                     .setTitle(R.string.unknown_apps_dlg_title)
164                     .setMessage(R.string.unknown_apps_dlg_text)
165                     .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
166                         public void onClick(DialogInterface dialog, int which) {
167                             Log.i(TAG, "Finishing off activity so that user can navigate to settings manually");
168                             finish();
169                         }})
170                     .setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() {
171                         public void onClick(DialogInterface dialog, int which) {
172                             Log.i(TAG, "Launching settings");
173                             launchSettingsAppAndFinish();
174                         }
175                     })
176                     .setOnCancelListener(this)
177                     .create();
178         case DLG_PACKAGE_ERROR :
179             return new AlertDialog.Builder(this)
180                     .setTitle(R.string.Parse_error_dlg_title)
181                     .setMessage(R.string.Parse_error_dlg_text)
182                     .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
183                         public void onClick(DialogInterface dialog, int which) {
184                             finish();
185                         }
186                     })
187                     .setOnCancelListener(this)
188                     .create();
189         case DLG_OUT_OF_SPACE:
190             // Guaranteed not to be null. will default to package name if not set by app
191             CharSequence appTitle = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
192             String dlgText = getString(R.string.out_of_space_dlg_text,
193                     appTitle.toString());
194             return new AlertDialog.Builder(this)
195                     .setTitle(R.string.out_of_space_dlg_title)
196                     .setMessage(dlgText)
197                     .setPositiveButton(R.string.manage_applications, new DialogInterface.OnClickListener() {
198                         public void onClick(DialogInterface dialog, int which) {
199                             //launch manage applications
200                             Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
201                             startActivity(intent);
202                             finish();
203                         }
204                     })
205                     .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
206                         public void onClick(DialogInterface dialog, int which) {
207                             Log.i(TAG, "Canceling installation");
208                             finish();
209                         }
210                   })
211                   .setOnCancelListener(this)
212                   .create();
213         case DLG_INSTALL_ERROR :
214             // Guaranteed not to be null. will default to package name if not set by app
215             CharSequence appTitle1 = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
216             String dlgText1 = getString(R.string.install_failed_msg,
217                     appTitle1.toString());
218             return new AlertDialog.Builder(this)
219                     .setTitle(R.string.install_failed)
220                     .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
221                         public void onClick(DialogInterface dialog, int which) {
222                             finish();
223                         }
224                     })
225                     .setMessage(dlgText1)
226                     .setOnCancelListener(this)
227                     .create();
228        }
229        return null;
230    }
231 
232     private class ClearCacheReceiver extends BroadcastReceiver {
233         public static final String INTENT_CLEAR_CACHE =
234                 "com.android.packageinstaller.CLEAR_CACHE";
235         @Override
236         public void onReceive(Context context, Intent intent) {
237             Message msg = mHandler.obtainMessage(FREE_SPACE);
238             msg.arg1 = (getResultCode() ==1) ? SUCCEEDED : FAILED;
239             mHandler.sendMessage(msg);
240         }
241     }
242 
243     private void checkOutOfSpace(long size) {
244         if(localLOGV) Log.i(TAG, "Checking for "+size+" number of bytes");
245         if (mClearCacheReceiver == null) {
246             mClearCacheReceiver = new ClearCacheReceiver();
247         }
248         registerReceiver(mClearCacheReceiver,
249                 new IntentFilter(ClearCacheReceiver.INTENT_CLEAR_CACHE));
250         PendingIntent pi = PendingIntent.getBroadcast(this,
251                 0,  new Intent(ClearCacheReceiver.INTENT_CLEAR_CACHE), 0);
252         mPm.freeStorage(size, pi.getIntentSender());
253     }
254 
255     private void launchSettingsAppAndFinish() {
256         //Create an intent to launch SettingsTwo activity
257         Intent launchSettingsIntent = new Intent(Settings.ACTION_APPLICATION_SETTINGS);
258         startActivity(launchSettingsIntent);
259         finish();
260     }
261 
262     private boolean isInstallingUnknownAppsAllowed() {
263         return Settings.Secure.getInt(getContentResolver(),
264             Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 0;
265     }
266 
267     private void makeTempCopyAndInstall() {
268         // Check if package is already installed. display confirmation dialog if replacing pkg
269         try {
270             mAppInfo = mPm.getApplicationInfo(mPkgInfo.packageName,
271                     PackageManager.GET_UNINSTALLED_PACKAGES);
272         } catch (NameNotFoundException e) {
273             mAppInfo = null;
274         }
275         if (mAppInfo == null) {
276             startInstallConfirm();
277         } else {
278             if(localLOGV) Log.i(TAG, "Replacing existing package:"+
279                     mPkgInfo.applicationInfo.packageName);
280             showDialogInner(DLG_REPLACE_APP);
281         }
282     }
283 
284     @Override
285     protected void onCreate(Bundle icicle) {
286         super.onCreate(icicle);
287         //get intent information
288         final Intent intent = getIntent();
289         mPackageURI = intent.getData();
290         mPm = getPackageManager();
291         mPkgInfo = PackageUtil.getPackageInfo(mPackageURI);
292 
293         // Check for parse errors
294         if(mPkgInfo == null) {
295             Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
296             showDialogInner(DLG_PACKAGE_ERROR);
297             return;
298         }
299 
300         //set view
301         requestWindowFeature(Window.FEATURE_NO_TITLE);
302         setContentView(R.layout.install_start);
303         mInstallConfirm = findViewById(R.id.install_confirm_panel);
304         mInstallConfirm.setVisibility(View.INVISIBLE);
305         PackageUtil.initSnippetForNewApp(this, mPkgInfo.applicationInfo,
306                 R.id.app_snippet, mPackageURI);
307        //check setting
308         if(!isInstallingUnknownAppsAllowed()) {
309             //ask user to enable setting first
310             showDialogInner(DLG_UNKNOWN_APPS);
311             return;
312         }
313         //compute the size of the application. just an estimate
314         long size;
315         String apkPath = mPackageURI.getPath();
316         File apkFile = new File(apkPath);
317         //TODO? DEVISE BETTER HEAURISTIC
318         size = 4*apkFile.length();
319         checkOutOfSpace(size);
320     }
321 
322     // Generic handling when pressing back key
323     public void onCancel(DialogInterface dialog) {
324         finish();
325     }
326 
327     public void onClick(View v) {
328         if(v == mOk) {
329             // Start subactivity to actually install the application
330             Intent newIntent = new Intent();
331             newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
332                     mPkgInfo.applicationInfo);
333             newIntent.setData(mPackageURI);
334             newIntent.setClass(this, InstallAppProgress.class);
335             String installerPackageName = getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
336             if (installerPackageName != null) {
337                 newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName);
338             }
339             if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
340             startActivity(newIntent);
341             finish();
342         } else if(v == mCancel) {
343             // Cancel and finish
344             finish();
345         }
346     }
347 }
348