• 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 static android.content.pm.PackageInstaller.SessionParams.UID_UNKNOWN;
20 
21 import android.app.Activity;
22 import android.app.AlertDialog;
23 import android.app.Dialog;
24 import android.app.PendingIntent;
25 import android.content.ActivityNotFoundException;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.DialogInterface.OnCancelListener;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.PackageInfo;
34 import android.content.pm.PackageInstaller;
35 import android.content.pm.PackageManager;
36 import android.content.pm.PackageManager.NameNotFoundException;
37 import android.content.pm.PackageParser;
38 import android.content.pm.ResolveInfo;
39 import android.net.Uri;
40 import android.os.Bundle;
41 import android.os.Handler;
42 import android.os.HandlerThread;
43 import android.os.Message;
44 import android.util.Log;
45 import android.view.View;
46 import android.widget.Button;
47 import android.widget.ImageView;
48 import android.widget.ProgressBar;
49 import android.widget.TextView;
50 import com.android.packageinstaller.permission.utils.IoUtils;
51 
52 import java.io.File;
53 import java.io.FileInputStream;
54 import java.io.IOException;
55 import java.io.InputStream;
56 import java.io.OutputStream;
57 import java.util.List;
58 
59 /**
60  * This activity corresponds to a download progress screen that is displayed
61  * when the user tries
62  * to install an application bundled as an apk file. The result of the application install
63  * is indicated in the result code that gets set to the corresponding installation status
64  * codes defined in PackageManager. If the package being installed already exists,
65  * the existing package is replaced with the new one.
66  */
67 public class InstallAppProgress extends Activity implements View.OnClickListener, OnCancelListener {
68     private final String TAG="InstallAppProgress";
69     private static final String BROADCAST_ACTION =
70             "com.android.packageinstaller.ACTION_INSTALL_COMMIT";
71     private static final String BROADCAST_SENDER_PERMISSION =
72             "android.permission.INSTALL_PACKAGES";
73     private ApplicationInfo mAppInfo;
74     private Uri mPackageURI;
75     private ProgressBar mProgressBar;
76     private View mOkPanel;
77     private TextView mStatusTextView;
78     private TextView mExplanationTextView;
79     private Button mDoneButton;
80     private Button mLaunchButton;
81     private final int INSTALL_COMPLETE = 1;
82     private Intent mLaunchIntent;
83     private static final int DLG_OUT_OF_SPACE = 1;
84     private CharSequence mLabel;
85     private HandlerThread mInstallThread;
86     private Handler mInstallHandler;
87 
88     private Handler mHandler = new Handler() {
89         public void handleMessage(Message msg) {
90             switch (msg.what) {
91                 case INSTALL_COMPLETE:
92                     if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
93                         Intent result = new Intent();
94                         result.putExtra(Intent.EXTRA_INSTALL_RESULT, msg.arg1);
95                         setResult(msg.arg1 == PackageInstaller.STATUS_SUCCESS
96                                 ? Activity.RESULT_OK : Activity.RESULT_FIRST_USER,
97                                         result);
98                         clearCachedApkIfNeededAndFinish();
99                         return;
100                     }
101                     // Update the status text
102                     mProgressBar.setVisibility(View.GONE);
103                     // Show the ok button
104                     int centerTextLabel;
105                     int centerExplanationLabel = -1;
106                     if (msg.arg1 == PackageInstaller.STATUS_SUCCESS) {
107                         mLaunchButton.setVisibility(View.VISIBLE);
108                         ((ImageView)findViewById(R.id.center_icon))
109                                 .setImageDrawable(getDrawable(R.drawable.ic_done_92));
110                         centerTextLabel = R.string.install_done;
111                         // Enable or disable launch button
112                         mLaunchIntent = getPackageManager().getLaunchIntentForPackage(
113                                 mAppInfo.packageName);
114                         boolean enabled = false;
115                         if(mLaunchIntent != null) {
116                             List<ResolveInfo> list = getPackageManager().
117                                     queryIntentActivities(mLaunchIntent, 0);
118                             if (list != null && list.size() > 0) {
119                                 enabled = true;
120                             }
121                         }
122                         if (enabled) {
123                             mLaunchButton.setOnClickListener(InstallAppProgress.this);
124                         } else {
125                             mLaunchButton.setEnabled(false);
126                         }
127                     } else if (msg.arg1 == PackageInstaller.STATUS_FAILURE_STORAGE){
128                         showDialogInner(DLG_OUT_OF_SPACE);
129                         return;
130                     } else {
131                         // Generic error handling for all other error codes.
132                         ((ImageView)findViewById(R.id.center_icon))
133                                 .setImageDrawable(getDrawable(R.drawable.ic_report_problem_92));
134                         centerExplanationLabel = getExplanationFromErrorCode(msg.arg1);
135                         centerTextLabel = R.string.install_failed;
136                         mLaunchButton.setVisibility(View.GONE);
137                     }
138                     if (centerExplanationLabel != -1) {
139                         mExplanationTextView.setText(centerExplanationLabel);
140                         findViewById(R.id.center_view).setVisibility(View.GONE);
141                         ((TextView)findViewById(R.id.explanation_status)).setText(centerTextLabel);
142                         findViewById(R.id.explanation_view).setVisibility(View.VISIBLE);
143                     } else {
144                         ((TextView)findViewById(R.id.center_text)).setText(centerTextLabel);
145                         findViewById(R.id.center_view).setVisibility(View.VISIBLE);
146                         findViewById(R.id.explanation_view).setVisibility(View.GONE);
147                     }
148                     mDoneButton.setOnClickListener(InstallAppProgress.this);
149                     mOkPanel.setVisibility(View.VISIBLE);
150                     break;
151                 default:
152                     break;
153             }
154         }
155     };
156 
157     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
158         @Override
159         public void onReceive(Context context, Intent intent) {
160             final int statusCode = intent.getIntExtra(
161                     PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
162             if (statusCode == PackageInstaller.STATUS_PENDING_USER_ACTION) {
163                 context.startActivity((Intent)intent.getParcelableExtra(Intent.EXTRA_INTENT));
164             } else {
165                 onPackageInstalled(statusCode);
166             }
167         }
168     };
169 
getExplanationFromErrorCode(int errCode)170     private int getExplanationFromErrorCode(int errCode) {
171         Log.d(TAG, "Installation error code: " + errCode);
172         switch (errCode) {
173             case PackageInstaller.STATUS_FAILURE_BLOCKED:
174                 return R.string.install_failed_blocked;
175             case PackageInstaller.STATUS_FAILURE_CONFLICT:
176                 return R.string.install_failed_conflict;
177             case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE:
178                 return R.string.install_failed_incompatible;
179             case PackageInstaller.STATUS_FAILURE_INVALID:
180                 return R.string.install_failed_invalid_apk;
181             default:
182                 return -1;
183         }
184     }
185 
186     @Override
onCreate(Bundle icicle)187     public void onCreate(Bundle icicle) {
188         super.onCreate(icicle);
189         Intent intent = getIntent();
190         mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
191         mPackageURI = intent.getData();
192 
193         final String scheme = mPackageURI.getScheme();
194         if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
195             throw new IllegalArgumentException("unexpected scheme " + scheme);
196         }
197 
198         mInstallThread = new HandlerThread("InstallThread");
199         mInstallThread.start();
200         mInstallHandler = new Handler(mInstallThread.getLooper());
201 
202         IntentFilter intentFilter = new IntentFilter();
203         intentFilter.addAction(BROADCAST_ACTION);
204         registerReceiver(
205                 mBroadcastReceiver, intentFilter, BROADCAST_SENDER_PERMISSION, null /*scheduler*/);
206 
207         initView();
208     }
209 
210     @Override
onBackPressed()211     public void onBackPressed() {
212         clearCachedApkIfNeededAndFinish();
213     }
214 
215     @SuppressWarnings("deprecation")
216     @Override
onCreateDialog(int id, Bundle bundle)217     public Dialog onCreateDialog(int id, Bundle bundle) {
218         switch (id) {
219         case DLG_OUT_OF_SPACE:
220             String dlgText = getString(R.string.out_of_space_dlg_text, mLabel);
221             return new AlertDialog.Builder(this)
222                     .setTitle(R.string.out_of_space_dlg_title)
223                     .setMessage(dlgText)
224                     .setPositiveButton(R.string.manage_applications, new DialogInterface.OnClickListener() {
225                         public void onClick(DialogInterface dialog, int which) {
226                             //launch manage applications
227                             Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
228                             startActivity(intent);
229                             clearCachedApkIfNeededAndFinish();
230                         }
231                     })
232                     .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
233                         public void onClick(DialogInterface dialog, int which) {
234                             Log.i(TAG, "Canceling installation");
235                             clearCachedApkIfNeededAndFinish();
236                         }
237                     })
238                     .setOnCancelListener(this)
239                     .create();
240         }
241        return null;
242    }
243 
244     @SuppressWarnings("deprecation")
245     private void showDialogInner(int id) {
246         removeDialog(id);
247         showDialog(id);
248     }
249 
250     void onPackageInstalled(int statusCode) {
251         Message msg = mHandler.obtainMessage(INSTALL_COMPLETE);
252         msg.arg1 = statusCode;
253         mHandler.sendMessage(msg);
254     }
255 
256     int getInstallFlags(String packageName) {
257         PackageManager pm = getPackageManager();
258         try {
259             PackageInfo pi =
260                     pm.getPackageInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
261             if (pi != null) {
262                 return PackageManager.INSTALL_REPLACE_EXISTING;
263             }
264         } catch (NameNotFoundException e) {
265         }
266         return 0;
267     }
268 
269     private void doPackageStage(PackageManager pm, PackageInstaller.SessionParams params) {
270         final PackageInstaller packageInstaller = pm.getPackageInstaller();
271         PackageInstaller.Session session = null;
272         try {
273             final String packageLocation = mPackageURI.getPath();
274             final File file = new File(packageLocation);
275             final int sessionId = packageInstaller.createSession(params);
276             final byte[] buffer = new byte[65536];
277 
278             session = packageInstaller.openSession(sessionId);
279 
280             final InputStream in = new FileInputStream(file);
281             final long sizeBytes = file.length();
282             final OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes);
283             try {
284                 int c;
285                 while ((c = in.read(buffer)) != -1) {
286                     out.write(buffer, 0, c);
287                     if (sizeBytes > 0) {
288                         final float fraction = ((float) c / (float) sizeBytes);
289                         session.addProgress(fraction);
290                     }
291                 }
292                 session.fsync(out);
293             } finally {
294                 IoUtils.closeQuietly(in);
295                 IoUtils.closeQuietly(out);
296             }
297 
298             // Create a PendingIntent and use it to generate the IntentSender
299             Intent broadcastIntent = new Intent(BROADCAST_ACTION);
300             PendingIntent pendingIntent = PendingIntent.getBroadcast(
301                     InstallAppProgress.this /*context*/,
302                     sessionId,
303                     broadcastIntent,
304                     PendingIntent.FLAG_UPDATE_CURRENT);
305             session.commit(pendingIntent.getIntentSender());
306         } catch (IOException e) {
307             onPackageInstalled(PackageInstaller.STATUS_FAILURE);
308         } finally {
309             IoUtils.closeQuietly(session);
310         }
311     }
312 
313     void initView() {
314         setContentView(R.layout.op_progress);
315 
316         final PackageUtil.AppSnippet as;
317         final PackageManager pm = getPackageManager();
318         final int installFlags = getInstallFlags(mAppInfo.packageName);
319 
320         if((installFlags & PackageManager.INSTALL_REPLACE_EXISTING )!= 0) {
321             Log.w(TAG, "Replacing package:" + mAppInfo.packageName);
322         }
323         if ("package".equals(mPackageURI.getScheme())) {
324             as = new PackageUtil.AppSnippet(pm.getApplicationLabel(mAppInfo),
325                     pm.getApplicationIcon(mAppInfo));
326         } else {
327             final File sourceFile = new File(mPackageURI.getPath());
328             as = PackageUtil.getAppSnippet(this, mAppInfo, sourceFile);
329         }
330         mLabel = as.label;
331         PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
332         mStatusTextView = (TextView)findViewById(R.id.center_text);
333         mExplanationTextView = (TextView) findViewById(R.id.explanation);
334         mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
335         mProgressBar.setIndeterminate(true);
336         // Hide button till progress is being displayed
337         mOkPanel = findViewById(R.id.buttons_panel);
338         mDoneButton = (Button)findViewById(R.id.done_button);
339         mLaunchButton = (Button)findViewById(R.id.launch_button);
340         mOkPanel.setVisibility(View.INVISIBLE);
341 
342         if ("package".equals(mPackageURI.getScheme())) {
343             try {
344                 pm.installExistingPackage(mAppInfo.packageName);
345                 onPackageInstalled(PackageInstaller.STATUS_SUCCESS);
346             } catch (PackageManager.NameNotFoundException e) {
347                 onPackageInstalled(PackageInstaller.STATUS_FAILURE_INVALID);
348             }
349         } else {
350             final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
351                     PackageInstaller.SessionParams.MODE_FULL_INSTALL);
352             params.referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
353             params.originatingUri = getIntent().getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
354             params.originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
355                     UID_UNKNOWN);
356 
357             File file = new File(mPackageURI.getPath());
358             try {
359                 params.setInstallLocation(PackageParser.parsePackageLite(file, 0).installLocation);
360             } catch (PackageParser.PackageParserException e) {
361                 Log.e(TAG, "Cannot parse package " + file + ". Assuming defaults.");
362             }
363 
364             mInstallHandler.post(new Runnable() {
365                 @Override
366                 public void run() {
367                     doPackageStage(pm, params);
368                 }
369             });
370         }
371     }
372 
373     @Override
374     protected void onDestroy() {
375         super.onDestroy();
376         unregisterReceiver(mBroadcastReceiver);
377         mInstallThread.getLooper().quitSafely();
378     }
379 
380     public void onClick(View v) {
381         if(v == mDoneButton) {
382             if (mAppInfo.packageName != null) {
383                 Log.i(TAG, "Finished installing "+mAppInfo.packageName);
384             }
385             clearCachedApkIfNeededAndFinish();
386         } else if(v == mLaunchButton) {
387             try {
388                 startActivity(mLaunchIntent);
389             } catch (ActivityNotFoundException e) {
390                 Log.e(TAG, "Could not start activity", e);
391             }
392             clearCachedApkIfNeededAndFinish();
393         }
394     }
395 
396     public void onCancel(DialogInterface dialog) {
397         clearCachedApkIfNeededAndFinish();
398     }
399 
400     private void clearCachedApkIfNeededAndFinish() {
401         // If we are installing from a content:// the apk is copied in the cache
402         // dir and passed in here. As we aren't started for a result because our
403         // caller needs to be able to forward the result, here we make sure the
404         // staging file in the cache dir is removed.
405         if ("file".equals(mPackageURI.getScheme()) && mPackageURI.getPath() != null
406                 && mPackageURI.getPath().startsWith(getCacheDir().toString())) {
407             File file = new File(mPackageURI.getPath());
408             file.delete();
409         }
410         finish();
411     }
412 }
413