New API demo for installing/uninstalling applications.

Change-Id: Idaaf2003a6a34f2859887be32c82416ba4d06852
diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index 7b3fabd..a26bb88 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -984,6 +984,18 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".content.InstallApk" android:label="@string/activity_install_apk"
+                android:enabled="@bool/atLeastHoneycombMR2">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <provider android:name=".content.FileProvider"
+                  android:authorities="com.example.android.apis.content.FileProvider"
+                  android:enabled="@bool/atLeastHoneycombMR2" />
+
         <!-- ************************************* -->
         <!--     OS PACKAGE SAMPLES                -->
         <!-- ************************************* -->
diff --git a/samples/ApiDemos/assets/HelloActivity.apk b/samples/ApiDemos/assets/HelloActivity.apk
new file mode 100644
index 0000000..4e4b487
--- /dev/null
+++ b/samples/ApiDemos/assets/HelloActivity.apk
Binary files differ
diff --git a/samples/ApiDemos/res/layout/install_apk.xml b/samples/ApiDemos/res/layout/install_apk.xml
new file mode 100644
index 0000000..1482d2c
--- /dev/null
+++ b/samples/ApiDemos/res/layout/install_apk.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+
+<!-- Demonstrates starting and stopping a local service.
+     See corresponding Java code com.android.sdk.app.LocalSerice.java. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:padding="4dip"
+    android:gravity="center_horizontal"
+    android:layout_width="match_parent" android:layout_height="match_parent">
+
+    <TextView
+        android:layout_width="match_parent" android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:paddingBottom="4dip"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text="Demonstrates using the INSTALL_PACKAGE Intent to install an application."/>
+
+    <Button android:id="@+id/unknown_source"
+        android:layout_width="wrap_content" android:layout_height="wrap_content"
+        android:text="Unknown Source">
+    </Button>
+
+    <Button android:id="@+id/my_source"
+        android:layout_width="wrap_content" android:layout_height="wrap_content"
+        android:text="My Source">
+    </Button>
+
+    <Button android:id="@+id/replace"
+        android:layout_width="wrap_content" android:layout_height="wrap_content"
+        android:text="Replace">
+    </Button>
+
+    <Button android:id="@+id/uninstall"
+        android:layout_width="wrap_content" android:layout_height="wrap_content"
+        android:text="Uninstall">
+    </Button>
+
+    <Button android:id="@+id/uninstall_result"
+        android:layout_width="wrap_content" android:layout_height="wrap_content"
+        android:text="Uninstall w/Result">
+    </Button>
+
+</LinearLayout>
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index ddf7e39..45d4755 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -363,6 +363,8 @@
     <string name="pick_phone">Pick a Phone</string>
     <string name="pick_address">Pick an Address</string>
 
+    <string name="activity_install_apk">Content/Packages/Install Apk</string>
+
     <!-- ============================== -->
     <!--  app/intents examples strings     -->
     <!-- ============================== -->
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 0000000..156625a
--- /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<InputStream> {
+    @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 0000000..9036ee0
--- /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");
+    }
+}