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