From ddea282ae971624eaccd24157df3b42fb115764c Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Tue, 9 Aug 2011 19:36:44 -0700 Subject: [PATCH] New API demo for installing/uninstalling applications. Change-Id: Idaaf2003a6a34f2859887be32c82416ba4d06852 --- samples/ApiDemos/AndroidManifest.xml | 12 ++ samples/ApiDemos/assets/HelloActivity.apk | Bin 0 -> 5266 bytes samples/ApiDemos/res/layout/install_apk.xml | 56 ++++++ samples/ApiDemos/res/values/strings.xml | 2 + .../android/apis/content/FileProvider.java | 115 +++++++++++ .../android/apis/content/InstallApk.java | 179 ++++++++++++++++++ 6 files changed, 364 insertions(+) create mode 100644 samples/ApiDemos/assets/HelloActivity.apk create mode 100644 samples/ApiDemos/res/layout/install_apk.xml create mode 100644 samples/ApiDemos/src/com/example/android/apis/content/FileProvider.java create mode 100644 samples/ApiDemos/src/com/example/android/apis/content/InstallApk.java diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml index 7b3fabd34..a26bb8880 100644 --- a/samples/ApiDemos/AndroidManifest.xml +++ b/samples/ApiDemos/AndroidManifest.xml @@ -984,6 +984,18 @@ + + + + + + + + + diff --git a/samples/ApiDemos/assets/HelloActivity.apk b/samples/ApiDemos/assets/HelloActivity.apk new file mode 100644 index 0000000000000000000000000000000000000000..4e4b4875a980ba4ee11cbb5d38d500462375beae GIT binary patch literal 5266 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 literal 0 HcmV?d00001 diff --git a/samples/ApiDemos/res/layout/install_apk.xml b/samples/ApiDemos/res/layout/install_apk.xml new file mode 100644 index 000000000..1482d2c3a --- /dev/null +++ b/samples/ApiDemos/res/layout/install_apk.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml index ddf7e39f5..45d47554f 100644 --- a/samples/ApiDemos/res/values/strings.xml +++ b/samples/ApiDemos/res/values/strings.xml @@ -363,6 +363,8 @@ Pick a Phone Pick an Address + Content/Packages/Install Apk + diff --git a/samples/ApiDemos/src/com/example/android/apis/content/FileProvider.java b/samples/ApiDemos/src/com/example/android/apis/content/FileProvider.java new file mode 100644 index 000000000..156625a67 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/content/FileProvider.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2011 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.example.android.apis.content; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.ContentProvider.PipeDataWriter; +import android.content.res.AssetFileDescriptor; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +/** + * A very simple content provider that can serve arbitrary asset files from + * our .apk. + */ +public class FileProvider extends ContentProvider + implements PipeDataWriter { + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + // Don't support queries. + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + // Don't support inserts. + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + // Don't support deletes. + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + // Don't support updates. + return 0; + } + + @Override + public String getType(Uri uri) { + // For this sample, assume all files are .apks. + return "application/vnd.android.package-archive"; + } + + @Override + public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException { + // Try to open an asset with the given name. + try { + InputStream is = getContext().getAssets().open(uri.getPath()); + // Start a new thread that pipes the stream data back to the caller. + return new AssetFileDescriptor( + openPipeHelper(uri, null, null, is, this), 0, + AssetFileDescriptor.UNKNOWN_LENGTH); + } catch (IOException e) { + FileNotFoundException fnf = new FileNotFoundException("Unable to open " + uri); + throw fnf; + } + } + + @Override + public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType, + Bundle opts, InputStream args) { + // Transfer data from the asset to the pipe the client is reading. + byte[] buffer = new byte[8192]; + int n; + FileOutputStream fout = new FileOutputStream(output.getFileDescriptor()); + try { + while ((n=args.read(buffer)) >= 0) { + fout.write(buffer, 0, n); + } + } catch (IOException e) { + Log.i("InstallApk", "Failed transferring", e); + } finally { + try { + args.close(); + } catch (IOException e) { + } + try { + fout.close(); + } catch (IOException e) { + } + } + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/content/InstallApk.java b/samples/ApiDemos/src/com/example/android/apis/content/InstallApk.java new file mode 100644 index 000000000..9036ee031 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/content/InstallApk.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2011 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.example.android.apis.content; + +// Need the following import to get access to the app resources, since this +// class is in a sub-package. +import com.example.android.apis.R; + +import android.app.Activity; +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.ContentProvider.PipeDataWriter; +import android.content.res.AssetFileDescriptor; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + + +/** + * Demonstration of styled text resources. + */ +public class InstallApk extends Activity { + static final int REQUEST_INSTALL = 1; + static final int REQUEST_UNINSTALL = 2; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.install_apk); + + // Watch for button clicks. + Button button = (Button)findViewById(R.id.unknown_source); + button.setOnClickListener(mUnknownSourceListener); + button = (Button)findViewById(R.id.my_source); + button.setOnClickListener(mMySourceListener); + button = (Button)findViewById(R.id.replace); + button.setOnClickListener(mReplaceListener); + button = (Button)findViewById(R.id.uninstall); + button.setOnClickListener(mUninstallListener); + button = (Button)findViewById(R.id.uninstall_result); + button.setOnClickListener(mUninstallResultListener); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + if (requestCode == REQUEST_INSTALL) { + if (resultCode == Activity.RESULT_OK) { + Toast.makeText(this, "Install succeeded!", Toast.LENGTH_SHORT).show(); + } else if (resultCode == Activity.RESULT_CANCELED) { + Toast.makeText(this, "Install canceled!", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, "Install Failed!", Toast.LENGTH_SHORT).show(); + } + } else if (requestCode == REQUEST_UNINSTALL) { + if (resultCode == Activity.RESULT_OK) { + Toast.makeText(this, "Uninstall succeeded!", Toast.LENGTH_SHORT).show(); + } else if (resultCode == Activity.RESULT_CANCELED) { + Toast.makeText(this, "Uninstall canceled!", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, "Uninstall Failed!", Toast.LENGTH_SHORT).show(); + } + } + } + + private OnClickListener mUnknownSourceListener = new OnClickListener() { + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE); + intent.setData(Uri.fromFile(prepareApk("HelloActivity.apk"))); + startActivity(intent); + } + }; + + private OnClickListener mMySourceListener = new OnClickListener() { + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE); + intent.setData(Uri.fromFile(prepareApk("HelloActivity.apk"))); + intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); + intent.putExtra(Intent.EXTRA_RETURN_RESULT, true); + intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, + getApplicationInfo().packageName); + startActivityForResult(intent, REQUEST_INSTALL); + } + }; + + private OnClickListener mReplaceListener = new OnClickListener() { + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE); + intent.setData(Uri.fromFile(prepareApk("HelloActivity.apk"))); + intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); + intent.putExtra(Intent.EXTRA_RETURN_RESULT, true); + intent.putExtra(Intent.EXTRA_ALLOW_REPLACE, true); + intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, + getApplicationInfo().packageName); + startActivityForResult(intent, REQUEST_INSTALL); + } + }; + + private OnClickListener mUninstallListener = new OnClickListener() { + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE); + intent.setData(Uri.parse( + "package:com.example.android.helloactivity")); + startActivity(intent); + } + }; + + private OnClickListener mUninstallResultListener = new OnClickListener() { + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE); + intent.setData(Uri.parse( + "package:com.example.android.helloactivity")); + intent.putExtra(Intent.EXTRA_RETURN_RESULT, true); + startActivityForResult(intent, REQUEST_UNINSTALL); + } + }; + + private File prepareApk(String assetName) { + // Copy the given asset out into a file so that it can be installed. + // Returns the path to the file. + byte[] buffer = new byte[8192]; + InputStream is = null; + FileOutputStream fout = null; + try { + is = getAssets().open(assetName); + fout = openFileOutput("tmp.apk", Context.MODE_WORLD_READABLE); + int n; + while ((n=is.read(buffer)) >= 0) { + fout.write(buffer, 0, n); + } + } catch (IOException e) { + Log.i("InstallApk", "Failed transferring", e); + } finally { + try { + if (is != null) { + is.close(); + } + } catch (IOException e) { + } + try { + if (fout != null) { + fout.close(); + } + } catch (IOException e) { + } + } + + return getFileStreamPath("tmp.apk"); + } +}