From 91ec61c3dca241befdf4a3803c45b051a3e3124f Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 3 Mar 2009 19:32:31 -0800 Subject: auto import from //depot/cupcake/@135843 --- .../packageinstaller/InstallAppConfirmation.java | 126 +++++++ .../android/packageinstaller/InstallAppDone.java | 109 ++++++ .../packageinstaller/InstallAppProgress.java | 114 ++++++ .../packageinstaller/PackageInstallerActivity.java | 420 +++++++++++++++++++++ src/com/android/packageinstaller/PackageUtil.java | 121 ++++++ .../android/packageinstaller/UninstallAppDone.java | 72 ++++ .../packageinstaller/UninstallAppProgress.java | 91 +++++ .../packageinstaller/UninstallerActivity.java | 189 ++++++++++ 8 files changed, 1242 insertions(+) create mode 100755 src/com/android/packageinstaller/InstallAppConfirmation.java create mode 100755 src/com/android/packageinstaller/InstallAppDone.java create mode 100755 src/com/android/packageinstaller/InstallAppProgress.java create mode 100644 src/com/android/packageinstaller/PackageInstallerActivity.java create mode 100644 src/com/android/packageinstaller/PackageUtil.java create mode 100755 src/com/android/packageinstaller/UninstallAppDone.java create mode 100755 src/com/android/packageinstaller/UninstallAppProgress.java create mode 100755 src/com/android/packageinstaller/UninstallerActivity.java (limited to 'src') diff --git a/src/com/android/packageinstaller/InstallAppConfirmation.java b/src/com/android/packageinstaller/InstallAppConfirmation.java new file mode 100755 index 00000000..fe7126b9 --- /dev/null +++ b/src/com/android/packageinstaller/InstallAppConfirmation.java @@ -0,0 +1,126 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +package com.android.packageinstaller; + +import com.android.packageinstaller.R; +import java.util.ArrayList; +import android.widget.AppSecurityPermissions; +import android.app.Activity; +import android.content.pm.PackageManager; +import android.content.pm.PackageParser; +import android.content.pm.PermissionInfo; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * This activity corresponds to a confirmation screen that is displayed when the user tries + * to install an application bundled as an apk file. + * The intent that launches this activity should include the application information object + * of the application(to be installed) and a list of permission strings associated + * with the application. This information is displayed on the screen and installation is either + * continued or canceled based on the user response(click ok or cancel). + */ +public class InstallAppConfirmation extends Activity implements View.OnClickListener { + private final String TAG="InstallAppConfirmation"; + private boolean localLOGV = false; + private Button mOk; + private Button mCancel; + private ApplicationInfo mAppInfo; + private Uri mPkgURI; + private View mContentView; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + Intent intent = getIntent(); + if(localLOGV) Log.i(TAG, "intent="+intent); + mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); + mPkgURI = intent.getData(); + if(localLOGV) Log.i(TAG, "mAppInfo = "+mAppInfo); + initView(); + } + + public void initView() { + requestWindowFeature(Window.FEATURE_NO_TITLE); + String unknown = getString(R.string.unknown); + //set description + String desc = getString(R.string.security_settings_desc); + if(desc == null) { + desc = unknown; + } + LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mContentView = inflater.inflate(R.layout.install_confirm, null); + setContentView(mContentView); + //initialize views + PackageUtil.initAppSnippet(this, mAppInfo, R.id.app_snippet); + if(desc != null) { + ((TextView)findViewById(R.id.security_settings_desc)).setText(desc); + } + + + LinearLayout permsView = (LinearLayout) mContentView.findViewById( + R.id.permissions_section); + boolean permVisible = false; + PackageParser.Package pkg = PackageUtil.getPackageInfo(mPkgURI); + if(pkg != null) { + AppSecurityPermissions asp = new AppSecurityPermissions(this, pkg); + if(asp.getPermissionCount() > 0) { + permVisible = true; + permsView.setVisibility(View.VISIBLE); + LinearLayout securityList = (LinearLayout) permsView.findViewById( + R.id.security_settings_list); + securityList.addView(asp.getPermissionsView()); + } + } + if(!permVisible){ + permsView.setVisibility(View.GONE); + } + mOk = (Button)findViewById(R.id.ok_button); + mCancel = (Button)findViewById(R.id.cancel_button); + mOk.setOnClickListener(this); + mCancel.setOnClickListener(this); + } + + public void setResultAndReturn(int result) { + if(result == RESULT_CANCELED) Log.i(TAG, "Result has been canceled"); + if(result == RESULT_OK) Log.i(TAG, "result ok"); + setResult(result); + finish(); + } + + public void onClick(View v) { + int result = RESULT_CANCELED; + if(v == mOk) { + result = RESULT_OK; + setResultAndReturn(result); + } else if(v == mCancel) { + result = RESULT_CANCELED; + setResultAndReturn(result); + } + } +} diff --git a/src/com/android/packageinstaller/InstallAppDone.java b/src/com/android/packageinstaller/InstallAppDone.java new file mode 100755 index 00000000..5a3f7d65 --- /dev/null +++ b/src/com/android/packageinstaller/InstallAppDone.java @@ -0,0 +1,109 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +package com.android.packageinstaller; + +import com.android.packageinstaller.R; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.widget.Button; +import android.widget.TextView; + +/** + * This activity corresponds to a install status screen that is displayed + * when the user tries + * to install an application bundled as an apk file. The screen + * has two buttons to either launch the newly installed application + * or close the screen. The installation result and the package uri are passed through the + * intent that launches the activity. + */ +public class InstallAppDone extends Activity implements View.OnClickListener { + private final String TAG="InstallAppDone"; + private boolean localLOGV = false; + private ApplicationInfo mAppInfo; + private Button mDoneButton; + private Button mLaunchButton; + private boolean installFlag; + private Intent mLaunchIntent; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + Intent intent = getIntent(); + mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); + installFlag = intent.getBooleanExtra(PackageUtil.INTENT_ATTR_INSTALL_STATUS, true); + if(localLOGV) Log.i(TAG, "installFlag="+installFlag); + initView(); + } + + public void initView() { + requestWindowFeature(Window.FEATURE_NO_TITLE); + String unknown = getString(R.string.unknown); + setContentView(R.layout.install_done); + // Initialize views + PackageUtil.initAppSnippet(this, mAppInfo, R.id.app_snippet); + TextView centerText = (TextView)findViewById(R.id.center_text); + mDoneButton = (Button)findViewById(R.id.done_button); + mLaunchButton = (Button)findViewById(R.id.launch_button); + int centerTextDrawableId; + int centerTextLabel; + if(installFlag) { + mLaunchButton.setVisibility(View.VISIBLE); + centerTextDrawableId = R.drawable.button_indicator_finish; + centerTextLabel = R.string.install_done; + // Enable or disable launch button + try { + mLaunchIntent = getPackageManager().getLaunchIntentForPackage( + mAppInfo.packageName); + } catch (PackageManager.NameNotFoundException e) { + } + if(mLaunchIntent != null) { + mLaunchButton.setOnClickListener(this); + } else { + mLaunchButton.setEnabled(false); + } + } else { + centerTextDrawableId = com.android.internal.R.drawable.ic_bullet_key_permission; + centerTextLabel = R.string.install_failed; + mLaunchButton.setVisibility(View.INVISIBLE); + } + Drawable centerTextDrawable = getResources().getDrawable(centerTextDrawableId); + centerTextDrawable.setBounds(0, 0, + centerTextDrawable.getIntrinsicWidth(), + centerTextDrawable.getIntrinsicHeight()); + centerText.setCompoundDrawables(centerTextDrawable, null, null, null); + centerText.setText(getString(centerTextLabel)); + mDoneButton.setOnClickListener(this); + } + + public void onClick(View v) { + if(v == mDoneButton) { + Log.i(TAG, "Finished installing "+mAppInfo); + finish(); + } else if(v == mLaunchButton) { + startActivity(mLaunchIntent); + finish(); + } + } +} diff --git a/src/com/android/packageinstaller/InstallAppProgress.java b/src/com/android/packageinstaller/InstallAppProgress.java new file mode 100755 index 00000000..da1044c9 --- /dev/null +++ b/src/com/android/packageinstaller/InstallAppProgress.java @@ -0,0 +1,114 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +package com.android.packageinstaller; + +import com.android.packageinstaller.R; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageInstallObserver; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.Window; +import android.widget.ProgressBar; +import android.widget.TextView; + +/** + * This activity corresponds to a download progress screen that is displayed + * when the user tries + * to install an application bundled as an apk file. The result of the application install + * is indicated in the result code that gets set to the corresponding installation status + * codes defined in PackageManager. If the package being installed already exists, + * the existing package is replaced with the new one. + */ +public class InstallAppProgress extends Activity { + private final String TAG="InstallAppProgress"; + private boolean localLOGV = false; + private ApplicationInfo mAppInfo; + private Uri mPackageURI; + private ProgressBar mProgressBar; + private final int INSTALL_COMPLETE = 1; + private Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + switch (msg.what) { + case INSTALL_COMPLETE: + //finish the activity posting result + setResultAndFinish(msg.arg1); + break; + default: + break; + } + } + }; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + Intent intent = getIntent(); + mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); + mPackageURI = intent.getData(); + initView(); + } + + class PackageInstallObserver extends IPackageInstallObserver.Stub { + public void packageInstalled(String packageName, int returnCode) { + Message msg = mHandler.obtainMessage(INSTALL_COMPLETE); + msg.arg1 = returnCode; + mHandler.sendMessage(msg); + } + } + + void setResultAndFinish(int retCode) { + Intent data = new Intent(); + setResult(retCode); + finish(); + } + + public void initView() { + requestWindowFeature(Window.FEATURE_NO_TITLE); + String unknown = getString(R.string.unknown); + setContentView(R.layout.op_progress); + //initialize views + PackageUtil.initAppSnippet(this, mAppInfo, R.id.app_snippet); + TextView installTextView = (TextView)findViewById(R.id.center_text); + installTextView.setText(R.string.installing); + mProgressBar = (ProgressBar) findViewById(R.id.progress_bar); + mProgressBar.setIndeterminate(true); + // Set flag to replace package if already existing + int installFlags = 0; + PackageManager pm = getPackageManager(); + try { + PackageInfo pi = pm.getPackageInfo(mAppInfo.packageName, + PackageManager.GET_UNINSTALLED_PACKAGES); + if(pi != null) { + installFlags |= PackageManager.REPLACE_EXISTING_PACKAGE; + } + } catch (NameNotFoundException e) { + } + if((installFlags & PackageManager.REPLACE_EXISTING_PACKAGE )!= 0) { + Log.w(TAG, "Replacing package:"+mAppInfo.packageName); + } + PackageInstallObserver observer = new PackageInstallObserver(); + pm.installPackage(mPackageURI, observer, installFlags); + } +} diff --git a/src/com/android/packageinstaller/PackageInstallerActivity.java b/src/com/android/packageinstaller/PackageInstallerActivity.java new file mode 100644 index 00000000..29f05b43 --- /dev/null +++ b/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -0,0 +1,420 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +package com.android.packageinstaller; + +import com.android.packageinstaller.R; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.DialogInterface.OnCancelListener; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageDataObserver; +import android.content.pm.PackageManager; +import android.content.pm.PackageParser; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.Uri; +import android.os.Bundle; +import android.os.FileUtils; +import android.os.Handler; +import android.os.Message; +import android.provider.Settings; +import android.util.Log; +import android.view.Window; + +/* + * This activity is launched when a new application is installed via side loading + * The package is first parsed and the user is notified of parse errors via a dialog. + * If the package is successfully parsed, the user is notified to turn on the install unknown + * applications setting. A memory check is made at this point and the user is notified of out + * of memory conditions if any. If the package is already existing on the device, + * a confirmation dialog (to replace the existing package) is presented to the user. + * Based on the user response the package is then installed by launching InstallAppConfirm + * sub activity. All state transitions are handled in this activity + */ +public class PackageInstallerActivity extends Activity implements OnCancelListener { + private static final int INSTALL_INITIAL = 0; + private static final int INSTALL_CONFIRM = 1; + private static final int INSTALL_PROGRESS = 2; + private static final int INSTALL_DONE = 3; + private static final String TAG = "PackageInstaller"; + private Uri mPackageURI; + private boolean localLOGV = false; + private int mCurrentState = INSTALL_INITIAL; + PackageManager mPm; + private PackageParser.Package mPkgInfo; + private File mTmpFile; + private static final int SUCCEEDED = 1; + private static final int FAILED = 0; + // Broadcast receiver for clearing cache + ClearCacheReceiver mClearCacheReceiver; + private static final int HANDLER_BASE_MSG_IDX = 0; + private static final int FREE_SPACE = HANDLER_BASE_MSG_IDX + 1; + + // ApplicationInfo object primarily used for already existing applications + private ApplicationInfo mAppInfo = null; + + // Dialog identifiers used in showDialog + private static final int DLG_BASE = 0; + private static final int DLG_REPLACE_APP = DLG_BASE + 1; + private static final int DLG_UNKNOWN_APPS = DLG_BASE + 2; + private static final int DLG_PACKAGE_ERROR = DLG_BASE + 3; + private static final int DLG_OUT_OF_SPACE = DLG_BASE + 4; + private static final int DLG_INSTALL_ERROR = DLG_BASE + 5; + + private Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + switch (msg.what) { + case FREE_SPACE: + unregisterReceiver(mClearCacheReceiver); + if(msg.arg1 == SUCCEEDED) { + makeTempCopyAndInstall(); + } else { + showDialogInner(DLG_OUT_OF_SPACE); + } + break; + default: + break; + } + } + }; + + private void startInstallActivityClass(int requestCode, Class cls) { + Intent newIntent = new Intent(); + startInstallActivityClass(newIntent, requestCode, cls); + } + + private void startInstallActivityClass(Intent newIntent, int requestCode, Class cls) { + newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, + mPkgInfo.applicationInfo); + newIntent.setData(mPackageURI); + newIntent.setClass(this, cls); + if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI); + startActivityForResult(newIntent, requestCode); + } + + private void startInstallConfirm() { + Intent newIntent = new Intent(); + newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, + mPkgInfo.applicationInfo); + newIntent.setData(mPackageURI); + newIntent.setClass(this, InstallAppConfirmation.class); + startActivityForResult(newIntent, INSTALL_CONFIRM); + } + + private void startInstallProgress() { + startInstallActivityClass(INSTALL_PROGRESS, InstallAppProgress.class); + } + + private void startInstallDone() { + Intent newIntent = new Intent(Intent.ACTION_VIEW); + newIntent.putExtra(PackageUtil.INTENT_ATTR_INSTALL_STATUS, true); + startInstallActivityClass(newIntent, INSTALL_DONE, InstallAppDone.class); + } + + private void showDialogInner(int id) { + // TODO better fix for this? Remove dialog so that it gets created again + removeDialog(id); + showDialog(id); + } + + @Override + public Dialog onCreateDialog(int id) { + switch (id) { + case DLG_REPLACE_APP: + int msgId = R.string.dlg_app_replacement_statement; + // Customized text for system apps + if ((mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + msgId = R.string.dlg_sys_app_replacement_statement; + } + return new AlertDialog.Builder(this) + .setTitle(R.string.dlg_app_replacement_title) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + startInstallConfirm(); + }}) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + Log.i(TAG, "Canceling installation"); + finish(); + }}) + .setMessage(msgId) + .setOnCancelListener(this) + .create(); + case DLG_UNKNOWN_APPS: + return new AlertDialog.Builder(this) + .setTitle(R.string.unknown_apps_dlg_title) + .setMessage(R.string.unknown_apps_dlg_text) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + Log.i(TAG, "Finishing off activity so that user can navigate to settings manually"); + finish(); + }}) + .setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + Log.i(TAG, "Launching settings"); + launchSettingsAppAndFinish(); + } + }) + .setOnCancelListener(this) + .create(); + case DLG_PACKAGE_ERROR : + return new AlertDialog.Builder(this) + .setTitle(R.string.Parse_error_dlg_title) + .setMessage(R.string.Parse_error_dlg_text) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }) + .setOnCancelListener(this) + .create(); + case DLG_OUT_OF_SPACE: + // Guaranteed not to be null. will default to package name if not set by app + CharSequence appTitle = mPm.getApplicationLabel(mPkgInfo.applicationInfo); + String dlgText = getString(R.string.out_of_space_dlg_text, + appTitle.toString()); + return new AlertDialog.Builder(this) + .setTitle(R.string.out_of_space_dlg_title) + .setMessage(dlgText) + .setPositiveButton(R.string.manage_applications, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + //launch manage applications + Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE"); + startActivity(intent); + finish(); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + Log.i(TAG, "Canceling installation"); + finish(); + } + }) + .setOnCancelListener(this) + .create(); + case DLG_INSTALL_ERROR : + // Guaranteed not to be null. will default to package name if not set by app + CharSequence appTitle1 = mPm.getApplicationLabel(mPkgInfo.applicationInfo); + String dlgText1 = getString(R.string.install_failed_msg, + appTitle1.toString()); + return new AlertDialog.Builder(this) + .setTitle(R.string.install_failed) + .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }) + .setMessage(dlgText1) + .setOnCancelListener(this) + .create(); + } + return null; + } + + private class ClearCacheReceiver extends BroadcastReceiver { + public static final String INTENT_CLEAR_CACHE = + "com.android.packageinstaller.CLEAR_CACHE"; + @Override + public void onReceive(Context context, Intent intent) { + Message msg = mHandler.obtainMessage(FREE_SPACE); + msg.arg1 = (getResultCode() ==1) ? SUCCEEDED : FAILED; + mHandler.sendMessage(msg); + } + } + + private void checkOutOfSpace(long size) { + if(localLOGV) Log.i(TAG, "Checking for "+size+" number of bytes"); + if (mClearCacheReceiver == null) { + mClearCacheReceiver = new ClearCacheReceiver(); + } + registerReceiver(mClearCacheReceiver, + new IntentFilter(ClearCacheReceiver.INTENT_CLEAR_CACHE)); + PendingIntent pi = PendingIntent.getBroadcast(this, + 0, new Intent(ClearCacheReceiver.INTENT_CLEAR_CACHE), 0); + mPm.freeStorage(size, pi); + } + + private void launchSettingsAppAndFinish() { + //Create an intent to launch SettingsTwo activity + Intent launchSettingsIntent = new Intent(Settings.ACTION_APPLICATION_SETTINGS); + startActivity(launchSettingsIntent); + finish(); + } + + private boolean isInstallingUnknownAppsAllowed() { + return Settings.Secure.getInt(getContentResolver(), + Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 0; + } + + private File createTempPackageFile(String filePath) { + File tmpPackageFile; + int i = filePath.lastIndexOf("/"); + String tmpFileName; + if(i != -1) { + tmpFileName = filePath.substring(i+1); + } else { + tmpFileName = filePath; + } + FileOutputStream fos; + try { + fos=openFileOutput(tmpFileName, MODE_WORLD_READABLE); + } catch (FileNotFoundException e1) { + Log.e(TAG, "Error opening file "+tmpFileName); + return null; + } + try { + fos.close(); + } catch (IOException e) { + Log.e(TAG, "Error opening file "+tmpFileName); + return null; + } + tmpPackageFile=getFileStreamPath(tmpFileName); + File srcPackageFile = new File(filePath); + if (!FileUtils.copyFile(srcPackageFile, tmpPackageFile)) { + return null; + } + return tmpPackageFile; + } + + private void makeTempCopyAndInstall() { + //copy file to tmp dir + mTmpFile = createTempPackageFile(mPackageURI.getPath()); + if(mTmpFile == null) { + //display a dialog + Log.e(TAG, "Error copying file locally. Failed Installation"); + showDialogInner(DLG_OUT_OF_SPACE); + return; + } + mPackageURI = Uri.parse("file://"+mTmpFile.getPath()); + // Check if package is already installed. display confirmation dialog if replacing pkg + try { + mAppInfo = mPm.getApplicationInfo(mPkgInfo.packageName, + PackageManager.GET_UNINSTALLED_PACKAGES); + } catch (NameNotFoundException e) { + mAppInfo = null; + } + if (mAppInfo == null) { + startInstallConfirm(); + } else { + if(localLOGV) Log.i(TAG, "Replacing existing package:"+ + mPkgInfo.applicationInfo.packageName); + showDialogInner(DLG_REPLACE_APP); + } + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + //get intent information + final Intent intent = getIntent(); + mPackageURI = intent.getData(); + mPm = getPackageManager(); + mPkgInfo = PackageUtil.getPackageInfo(mPackageURI); + + // Check for parse errors + if(mPkgInfo == null) { + Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation"); + showDialogInner(DLG_PACKAGE_ERROR); + return; + } + + //set view + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.install_start); + PackageUtil.initAppSnippet(this, mPkgInfo.applicationInfo, R.id.app_snippet); + //check setting + if(!isInstallingUnknownAppsAllowed()) { + //ask user to enable setting first + showDialogInner(DLG_UNKNOWN_APPS); + return; + } + //compute the size of the application. just an estimate + long size; + String apkPath = mPackageURI.getPath(); + File apkFile = new File(apkPath); + //TODO? DEVISE BETTER HEAURISTIC + size = 4*apkFile.length(); + checkOutOfSpace(size); + } + + @Override + public void onDestroy() { + super.onDestroy(); + // Delete the temporary file if it still exists + if (mTmpFile != null) { + deleteFile(mTmpFile.getName()); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + boolean finish = true; + boolean removeTmpFile = false; + switch(requestCode) { + case INSTALL_CONFIRM: + if (resultCode == RESULT_OK) { + finish = false; + mCurrentState = INSTALL_PROGRESS; + startInstallProgress(); + } else { + removeTmpFile = true; + } + break; + case INSTALL_PROGRESS: + finish = false; + mCurrentState = INSTALL_DONE; + if (resultCode == PackageManager.INSTALL_SUCCEEDED) { + //start the next screen to show final status of installation + startInstallDone(); + } else { + showDialogInner(DLG_INSTALL_ERROR); + } + // Now that the package is installed just delete the temp file + removeTmpFile = true; + break; + case INSTALL_DONE: + //neednt check for result code here + break; + default: + break; + } + if ((removeTmpFile) && (mTmpFile != null)) { + deleteFile(mTmpFile.getName()); + } + if (finish) { + //finish off this activity to return to the previous activity that launched it + if (localLOGV) Log.i(TAG, "Finishing off activity"); + finish(); + } + } + + // Generic handling when pressing back key + public void onCancel(DialogInterface dialog) { + finish(); + } +} diff --git a/src/com/android/packageinstaller/PackageUtil.java b/src/com/android/packageinstaller/PackageUtil.java new file mode 100644 index 00000000..b36bcf2b --- /dev/null +++ b/src/com/android/packageinstaller/PackageUtil.java @@ -0,0 +1,121 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +package com.android.packageinstaller; + +import java.io.File; +import java.util.List; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageParser; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.util.DisplayMetrics; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +/** + * This is a utility class for defining some utility methods and constants + * used in the package installer application. + */ +public class PackageUtil { + public static final String PREFIX="com.android.packageinstaller."; + public static final String INTENT_ATTR_INSTALL_STATUS = PREFIX+"installStatus"; + public static final String INTENT_ATTR_APPLICATION_INFO=PREFIX+"applicationInfo"; + public static final String INTENT_ATTR_PERMISSIONS_LIST=PREFIX+"PermissionsList"; + //intent attribute strings related to uninstall + public static final String INTENT_ATTR_PACKAGE_NAME=PREFIX+"PackageName"; + + /* + * Utility method to get application information for a given packageURI + */ + public static ApplicationInfo getApplicationInfo(Uri packageURI) { + final String archiveFilePath = packageURI.getPath(); + PackageParser packageParser = new PackageParser(archiveFilePath); + File sourceFile = new File(archiveFilePath); + DisplayMetrics metrics = new DisplayMetrics(); + metrics.setToDefaults(); + PackageParser.Package pkg = packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0); + if (pkg == null) { + return null; + } + return pkg.applicationInfo; + } + + /* + * Utility method to get package information for a given packageURI + */ + public static PackageParser.Package getPackageInfo(Uri packageURI) { + final String archiveFilePath = packageURI.getPath(); + PackageParser packageParser = new PackageParser(archiveFilePath); + File sourceFile = new File(archiveFilePath); + DisplayMetrics metrics = new DisplayMetrics(); + metrics.setToDefaults(); + return packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0); + } + + /* + * Utility method to get application label from package manager for a given context + */ + public static CharSequence getApplicationLabel(Context context, ApplicationInfo appInfo) { + CharSequence appName = appInfo.loadLabel(context.getPackageManager()); + if(appName == null) { + appName = context.getString(R.string.unknown); + } + return appName; + } + + /* + * Utility method to getApplicationIcon from package manager for a given context + */ + public static Drawable getApplicationIcon(Context context, ApplicationInfo appInfo) { + return appInfo.loadIcon(context.getPackageManager()); + } + + /* + * Utility method to display application snippet. make sure to setContentView on context + * before invoking this method + */ + public static View initAppSnippet(Activity context, ApplicationInfo appInfo, int snippetId) { + View appSnippet = context.findViewById(snippetId); + ((ImageView)appSnippet.findViewById(R.id.app_icon)).setImageDrawable( + getApplicationIcon(context, appInfo)); + ((TextView)appSnippet.findViewById(R.id.app_name)).setText( + getApplicationLabel(context, appInfo)); + return appSnippet; + } + + public static boolean isPackageAlreadyInstalled(Activity context, String pkgName) { + List installedList = context.getPackageManager().getInstalledPackages( + PackageManager.GET_UNINSTALLED_PACKAGES); + int installedListSize = installedList.size(); + for(int i = 0; i < installedListSize; i++) { + PackageInfo tmp = installedList.get(i); + if(pkgName.equalsIgnoreCase(tmp.packageName)) { + return true; + } + + } + return false; + } +} diff --git a/src/com/android/packageinstaller/UninstallAppDone.java b/src/com/android/packageinstaller/UninstallAppDone.java new file mode 100755 index 00000000..73dffe3d --- /dev/null +++ b/src/com/android/packageinstaller/UninstallAppDone.java @@ -0,0 +1,72 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +package com.android.packageinstaller; + +import com.android.packageinstaller.R; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.widget.Button; +import android.widget.TextView; + +/** + * This activity corresponds to a uninstall status screen that is displayed + * when an application gets uninstalled. The screen contains a single ok button at the + * bottom. + */ +public class UninstallAppDone extends Activity implements View.OnClickListener { + private final String TAG="UninstallAppDone"; + private boolean localLOGV = false; + private ApplicationInfo mAppInfo; + private Button mOkButton; + private boolean uninstallFlag; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + Intent intent = getIntent(); + mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); + //TODO set installFlag + uninstallFlag = intent.getBooleanExtra(PackageUtil.INTENT_ATTR_INSTALL_STATUS, true); + initView(); + } + + public void initView() { + String unknown = getString(R.string.unknown); + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.uninstall_done); + TextView centerText = (TextView)findViewById(R.id.center_text); + if(uninstallFlag) { + centerText.setText(getString(R.string.uninstall_done)); + } else { + centerText.setText(R.string.uninstall_failed); + } + mOkButton = (Button)findViewById(R.id.ok_button); + mOkButton.setOnClickListener(this); + } + + public void onClick(View v) { + if(v == mOkButton) { + Log.i(TAG, "Finished installing "+mAppInfo); + finish(); + } + } +} diff --git a/src/com/android/packageinstaller/UninstallAppProgress.java b/src/com/android/packageinstaller/UninstallAppProgress.java new file mode 100755 index 00000000..6b30118b --- /dev/null +++ b/src/com/android/packageinstaller/UninstallAppProgress.java @@ -0,0 +1,91 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +package com.android.packageinstaller; + +import android.app.Activity; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageDeleteObserver; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.Window; +import android.view.ViewDebug; +import android.widget.ProgressBar; +import android.widget.TextView; + +/** + * This activity corresponds to a download progress screen that is displayed + * when an application is uninstalled. The result of the application uninstall + * is indicated in the result code that gets set to 0 or 1. The application gets launched + * by an intent with the intent's class name explicitly set to UninstallAppProgress and expects + * the application object of the application to uninstall. + */ +public class UninstallAppProgress extends Activity { + private final String TAG="UninstallAppProgress"; + private boolean localLOGV = false; + private ApplicationInfo mAppInfo; + private final int UNINSTALL_COMPLETE = 1; + public final static int SUCCEEDED=1; + public final static int FAILED=0; + private Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + switch (msg.what) { + case UNINSTALL_COMPLETE: + //finish the activity posting result + setResultAndFinish(msg.arg1); + break; + default: + break; + } + } + }; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + Intent intent = getIntent(); + mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); + initView(); + } + + class PackageDeleteObserver extends IPackageDeleteObserver.Stub { + public void packageDeleted(boolean succeeded) { + Message msg = mHandler.obtainMessage(UNINSTALL_COMPLETE); + msg.arg1 = succeeded?SUCCEEDED:FAILED; + mHandler.sendMessage(msg); + } + } + + void setResultAndFinish(int retCode) { + setResult(retCode); + finish(); + } + + public void initView() { + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.op_progress); + //initialize views + PackageUtil.initAppSnippet(this, mAppInfo, R.id.app_snippet); + TextView installTextView = (TextView)findViewById(R.id.center_text); + installTextView.setText(R.string.uninstalling); + final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar); + progressBar.setIndeterminate(true); + PackageDeleteObserver observer = new PackageDeleteObserver(); + getPackageManager().deletePackage(mAppInfo.packageName, observer, 0); + } +} diff --git a/src/com/android/packageinstaller/UninstallerActivity.java b/src/com/android/packageinstaller/UninstallerActivity.java new file mode 100755 index 00000000..f22cc28a --- /dev/null +++ b/src/com/android/packageinstaller/UninstallerActivity.java @@ -0,0 +1,189 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +package com.android.packageinstaller; + +import com.android.packageinstaller.R; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.content.pm.PackageManager.NameNotFoundException; + +/* + * This activity presents UI to uninstall an application. Usually launched with intent + * Intent.ACTION_UNINSTALL_PKG_COMMAND and attribute + * com.android.packageinstaller.PackageName set to the application package name + */ +public class UninstallerActivity extends Activity implements OnClickListener { + private static final String TAG = "UninstallerActivity"; + private boolean localLOGV = false; + // States indicating status of ui display when uninstalling application + private static final int UNINSTALL_CONFIRM = 1; + private static final int UNINSTALL_PROGRESS = 2; + private static final int UNINSTALL_DONE = 3; + private int mCurrentState = UNINSTALL_CONFIRM; + PackageManager mPm; + private ApplicationInfo mAppInfo; + private Button mOk; + private Button mCancel; + + // Dialog identifiers used in showDialog + private static final int DLG_BASE = 0; + private static final int DLG_APP_NOT_FOUND = DLG_BASE + 1; + private static final int DLG_UNINSTALL_FAILED = DLG_BASE + 2; + + private void showDialogInner(int id) { + showDialog(id); + } + + @Override + public Dialog onCreateDialog(int id) { + switch (id) { + case DLG_APP_NOT_FOUND : + return new AlertDialog.Builder(this) + .setTitle(R.string.app_not_found_dlg_title) + .setIcon(com.android.internal.R.drawable.ic_dialog_alert) + .setMessage(R.string.app_not_found_dlg_text) + .setNeutralButton(getString(R.string.dlg_ok), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + finish(); + }}) + .create(); + case DLG_UNINSTALL_FAILED : + // Guaranteed not to be null. will default to package name if not set by app + CharSequence appTitle = mPm.getApplicationLabel(mAppInfo); + String dlgText = getString(R.string.uninstall_failed_msg, + appTitle.toString()); + // Display uninstall failed dialog + return new AlertDialog.Builder(this) + .setTitle(R.string.uninstall_failed) + .setIcon(com.android.internal.R.drawable.ic_dialog_alert) + .setMessage(dlgText) + .setNeutralButton(getString(R.string.dlg_ok), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + finish(); + }}) + .create(); + } + return null; + } + + private void startUninstallProgress() { + Intent newIntent = new Intent(Intent.ACTION_VIEW); + newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, + mAppInfo); + newIntent.setClass(this, UninstallAppProgress.class); + startActivityForResult(newIntent, UNINSTALL_PROGRESS); + } + + private void startUninstallDone() { + Intent newIntent = new Intent(Intent.ACTION_VIEW); + newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, + mAppInfo); + newIntent.putExtra(PackageUtil.INTENT_ATTR_INSTALL_STATUS, true); + newIntent.setClass(this, UninstallAppDone.class); + startActivityForResult(newIntent, UNINSTALL_DONE); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + //get intent information + final Intent intent = getIntent(); + Uri packageURI = intent.getData(); + String packageName = packageURI.getEncodedSchemeSpecificPart(); + if(packageName == null) { + Log.e(TAG, "Invalid package name:"+packageName); + showDialog(DLG_APP_NOT_FOUND); + return; + } + //initialize package manager + mPm = getPackageManager(); + boolean errFlag = false; + try { + mAppInfo = mPm.getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES); + } catch (NameNotFoundException e) { + errFlag = true; + } + if(mAppInfo == null || errFlag) { + Log.e(TAG, "Invalid application:"+packageName); + showDialog(DLG_APP_NOT_FOUND); + } else { + requestWindowFeature(Window.FEATURE_NO_TITLE); + //set view + setContentView(R.layout.uninstall_confirm); + PackageUtil.initAppSnippet(this, mAppInfo, R.id.app_snippet); + //initialize ui elements + mOk = (Button)findViewById(R.id.ok_button); + mCancel = (Button)findViewById(R.id.cancel_button); + mOk.setOnClickListener(this); + mCancel.setOnClickListener(this); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + boolean finish = true; + switch(requestCode) { + case UNINSTALL_PROGRESS: + finish = false; + mCurrentState = UNINSTALL_DONE; + //start the next screen to show final status of installation + if (resultCode==UninstallAppProgress.SUCCEEDED) { + startUninstallDone(); + } else { + showDialogInner(DLG_UNINSTALL_FAILED); + } + break; + case UNINSTALL_DONE: + //neednt check for result code here + break; + default: + break; + } + if(finish) { + //finish off this activity to return to the previous activity that launched it + Log.i(TAG, "Finishing off activity"); + finish(); + } + } + + private void finishAndReturn() { + finish(); + } + + public void onClick(View v) { + if(v == mOk) { + //initiate next screen + startUninstallProgress(); + } else if(v == mCancel) { + finishAndReturn(); + } + } +} -- cgit v1.2.3