am a7d868d4: Merge "Add toast when an app intercepts the launch of another app." into gingerbread

Merge commit 'a7d868d4f99dfaf85e13498210aecf1ad8efd859' into gingerbread-plus-aosp

* commit 'a7d868d4f99dfaf85e13498210aecf1ad8efd859':
  Add toast when an app intercepts the launch of another app.
diff --git a/core/res/res/layout/launch_warning.xml b/core/res/res/layout/launch_warning.xml
new file mode 100644
index 0000000..1923ff0
--- /dev/null
+++ b/core/res/res/layout/launch_warning.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2010, 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.
+*/
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <ImageView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="6dp"
+        android:paddingBottom="6dp"
+        android:scaleType="fitXY"
+        android:gravity="fill_horizontal"
+        android:src="@android:drawable/divider_horizontal_dark" />
+            
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="10dip"
+        android:orientation="horizontal">
+        <ImageView android:id="@+id/replace_app_icon"
+            android:layout_width="@android:dimen/app_icon_size"
+            android:layout_height="@android:dimen/app_icon_size"
+            android:scaleType="fitCenter" />
+        <TextView android:id="@+id/replace_message"
+            style="?android:attr/textAppearanceMedium"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:padding="5dip" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="10dip"
+        android:orientation="horizontal">
+        <ImageView android:id="@+id/original_app_icon"
+            android:layout_width="@android:dimen/app_icon_size"
+            android:layout_height="@android:dimen/app_icon_size"
+            android:scaleType="fitCenter" />
+        <TextView android:id="@+id/original_message"
+            style="?android:attr/textAppearanceMedium"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:padding="5dip" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 29ac2ea..5913639 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1921,6 +1921,12 @@
     <string name="report">Report</string>
     <!-- Button allowing the user to choose to wait for an application that is not responding to become responsive again. -->
     <string name="wait">Wait</string>
+    <!-- [CHAR LIMIT=25] Title of the alert when application launches on top of another. -->
+    <string name="launch_warning_title">Application redirected</string>
+    <!-- [CHAR LIMIT=50] Title of the alert when application launches on top of another. -->
+    <string name="launch_warning_replace"><xliff:g id="app_name">%1$s</xliff:g> is now running.</string>
+    <!-- [CHAR LIMIT=50] Title of the alert when application launches on top of another. -->
+    <string name="launch_warning_original"><xliff:g id="app_name">%1$s</xliff:g> was originally launched.</string>
 
     <!-- Text of the alert that is displayed when an application has violated StrictMode. -->
     <string name="smv_application">The application <xliff:g id="application">%1$s</xliff:g>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index d585d9e..f7bd157 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -521,5 +521,12 @@
         <item name="android:windowAnimationStyle">@android:style/Animation.RecentApplications</item>
         <item name="android:textColor">@android:color/secondary_text_nofocus</item>
     </style>
+
+    <!-- Default theme for window that looks like a toast. -->
+    <style name="Theme.Toast" parent="@android:style/Theme.Dialog">
+        <item name="android:windowBackground">@android:drawable/toast_frame</item>
+        <item name="android:windowAnimationStyle">@android:style/Animation.Toast</item>
+        <item name="android:backgroundDimEnabled">false</item>
+    </style>
     
 </resources>
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index f47ced9..db41668 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -753,6 +753,7 @@
     boolean mWaitingUpdate = false;
     boolean mDidUpdate = false;
     boolean mOnBattery = false;
+    boolean mLaunchWarningShown = false;
 
     Context mContext;
 
@@ -2903,6 +2904,30 @@
         }
     }
 
+    final void showLaunchWarningLocked(final ActivityRecord cur, final ActivityRecord next) {
+        if (!mLaunchWarningShown) {
+            mLaunchWarningShown = true;
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    synchronized (ActivityManagerService.this) {
+                        final Dialog d = new LaunchWarningWindow(mContext, cur, next);
+                        d.show();
+                        mHandler.postDelayed(new Runnable() {
+                            @Override
+                            public void run() {
+                                synchronized (ActivityManagerService.this) {
+                                    d.dismiss();
+                                    mLaunchWarningShown = false;
+                                }
+                            }
+                        }, 4000);
+                    }
+                }
+            });
+        }
+    }
+    
     final void decPersistentCountLocked(ProcessRecord app) {
         app.persistentActivities--;
         if (app.persistentActivities > 0) {
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index 1687db1..9358469 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -34,6 +34,7 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
+import android.util.TimeUtils;
 import android.view.IApplicationToken;
 
 import java.io.PrintWriter;
@@ -68,7 +69,8 @@
     int icon;               // resource identifier of activity's icon.
     int theme;              // resource identifier of activity's theme.
     TaskRecord task;        // the task this is in.
-    long startTime;         // when we starting launching this activity
+    long launchTime;        // when we starting launching this activity
+    long startTime;         // last time this activity was started
     long cpuTimeAtResume;   // the cpu time of host process at the time of resuming activity
     Configuration configuration; // configuration activity was last running in
     ActivityRecord resultTo; // who started this entry, so will get our reply
@@ -165,6 +167,11 @@
                 pw.print(" frozenBeforeDestroy="); pw.print(frozenBeforeDestroy);
                 pw.print(" thumbnailNeeded="); pw.print(thumbnailNeeded);
                 pw.print(" idle="); pw.println(idle);
+        if (launchTime != 0 || startTime != 0) {
+            pw.print(prefix); pw.print("launchTime=");
+                    TimeUtils.formatDuration(launchTime, pw); pw.print(" startTime=");
+                    TimeUtils.formatDuration(startTime, pw); pw.println("");
+        }
         if (waitingVisible || nowVisible) {
             pw.print(prefix); pw.print("waitingVisible="); pw.print(waitingVisible);
                     pw.print(" nowVisible="); pw.println(nowVisible);
@@ -417,9 +424,9 @@
     
     public void windowsVisible() {
         synchronized(service) {
-            if (startTime != 0) {
+            if (launchTime != 0) {
                 final long curTime = SystemClock.uptimeMillis();
-                final long thisTime = curTime - startTime;
+                final long thisTime = curTime - launchTime;
                 final long totalTime = stack.mInitialStartTime != 0
                         ? (curTime - stack.mInitialStartTime) : thisTime;
                 if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) {
@@ -428,22 +435,24 @@
                             thisTime, totalTime);
                     StringBuilder sb = service.mStringBuilder;
                     sb.setLength(0);
-                    sb.append("Displayed activity ");
+                    sb.append("Displayed ");
                     sb.append(shortComponentName);
                     sb.append(": ");
-                    sb.append(thisTime);
-                    sb.append(" ms (total ");
+                    TimeUtils.formatDuration(thisTime, sb);
+                    sb.append(" (total ");
+                    TimeUtils.formatDuration(totalTime, sb);
                     sb.append(totalTime);
-                    sb.append(" ms)");
+                    sb.append(")");
                     Log.i(ActivityManagerService.TAG, sb.toString());
                 }
                 stack.reportActivityLaunchedLocked(false, this, thisTime, totalTime);
                 if (totalTime > 0) {
                     service.mUsageStatsService.noteLaunchTime(realActivity, (int)totalTime);
                 }
-                startTime = 0;
+                launchTime = 0;
                 stack.mInitialStartTime = 0;
             }
+            startTime = 0;
             stack.reportActivityVisibleLocked(this);
             if (ActivityManagerService.DEBUG_SWITCH) Log.v(
                     ActivityManagerService.TAG, "windowsVisible(): " + this);
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 5f79b85..a0c21dd 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -102,6 +102,10 @@
     // 30 minutes.
     static final long ACTIVITY_INACTIVE_RESET_TIME = 1000*60*30;
     
+    // How long between activity launches that we consider safe to not warn
+    // the user about an unexpected activity being launched on top.
+    static final long START_WARN_TIME = 5*1000;
+    
     // Set to false to disable the preview that is shown while a new activity
     // is being started.
     static final boolean SHOW_APP_STARTING_PREVIEW = true;
@@ -213,6 +217,13 @@
     ActivityRecord mResumedActivity = null;
     
     /**
+     * This is the last activity that has been started.  It is only used to
+     * identify when multiple activities are started at once so that the user
+     * can be warned they may not be in the activity they think they are.
+     */
+    ActivityRecord mLastStartedActivity = null;
+    
+    /**
      * Set when we know we are going to be calling updateConfiguration()
      * soon, so want to skip intermediate config checks.
      */
@@ -586,10 +597,10 @@
         ProcessRecord app = mService.getProcessRecordLocked(r.processName,
                 r.info.applicationInfo.uid);
         
-        if (r.startTime == 0) {
-            r.startTime = SystemClock.uptimeMillis();
+        if (r.launchTime == 0) {
+            r.launchTime = SystemClock.uptimeMillis();
             if (mInitialStartTime == 0) {
-                mInitialStartTime = r.startTime;
+                mInitialStartTime = r.launchTime;
             }
         } else if (mInitialStartTime == 0) {
             mInitialStartTime = SystemClock.uptimeMillis();
@@ -1090,6 +1101,31 @@
             return false;
         }
 
+        // Okay we are now going to start a switch, to 'next'.  We may first
+        // have to pause the current activity, but this is an important point
+        // where we have decided to go to 'next' so keep track of that.
+        if (mLastStartedActivity != null) {
+            long now = SystemClock.uptimeMillis();
+            final boolean inTime = mLastStartedActivity.startTime != 0
+                    && (mLastStartedActivity.startTime + START_WARN_TIME) >= now;
+            final int lastUid = mLastStartedActivity.info.applicationInfo.uid;
+            final int nextUid = next.info.applicationInfo.uid;
+            if (inTime && lastUid != nextUid
+                    && lastUid != next.launchedFromUid
+                    && mService.checkPermission(
+                            android.Manifest.permission.STOP_APP_SWITCHES,
+                            -1, next.launchedFromUid)
+                    != PackageManager.PERMISSION_GRANTED) {
+                mService.showLaunchWarningLocked(mLastStartedActivity, next);
+            } else {
+                next.startTime = now;
+                mLastStartedActivity = next;
+            }
+        } else {
+            next.startTime = SystemClock.uptimeMillis();
+            mLastStartedActivity = next;
+        }
+        
         // We need to start pausing the current activity so the top one
         // can be resumed...
         if (mResumedActivity != null) {
@@ -1314,7 +1350,6 @@
         
         if (!newTask) {
             // If starting in an existing task, find where that is...
-            ActivityRecord next = null;
             boolean startIt = true;
             for (int i = NH-1; i >= 0; i--) {
                 ActivityRecord p = (ActivityRecord)mHistory.get(i);
@@ -1342,14 +1377,13 @@
                 if (p.fullscreen) {
                     startIt = false;
                 }
-                next = p;
             }
         }
 
         // Place a new activity at top of stack, so it is next to interact
         // with the user.
         if (addPos < 0) {
-            addPos = mHistory.size();
+            addPos = NH;
         }
         
         // If we are not placing the new activity frontmost, we do not want
diff --git a/services/java/com/android/server/am/LaunchWarningWindow.java b/services/java/com/android/server/am/LaunchWarningWindow.java
new file mode 100644
index 0000000..4130e33
--- /dev/null
+++ b/services/java/com/android/server/am/LaunchWarningWindow.java
@@ -0,0 +1,36 @@
+package com.android.server.am;
+
+import com.android.internal.R;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public class LaunchWarningWindow extends Dialog {
+    public LaunchWarningWindow(Context context, ActivityRecord cur, ActivityRecord next) {
+        super(context, R.style.Theme_Toast);
+
+        requestWindowFeature(Window.FEATURE_LEFT_ICON);
+        getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+        
+        setContentView(R.layout.launch_warning);
+        setTitle(context.getText(R.string.launch_warning_title));
+        getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,
+                R.drawable.ic_dialog_alert);
+        ImageView icon = (ImageView)findViewById(R.id.replace_app_icon);
+        icon.setImageDrawable(next.info.applicationInfo.loadIcon(context.getPackageManager()));
+        TextView text = (TextView)findViewById(R.id.replace_message);
+        text.setText(context.getResources().getString(R.string.launch_warning_replace,
+                next.info.applicationInfo.loadLabel(context.getPackageManager()).toString()));
+        icon = (ImageView)findViewById(R.id.original_app_icon);
+        icon.setImageDrawable(cur.info.applicationInfo.loadIcon(context.getPackageManager()));
+        text = (TextView)findViewById(R.id.original_message);
+        text.setText(context.getResources().getString(R.string.launch_warning_original,
+                cur.info.applicationInfo.loadLabel(context.getPackageManager()).toString()));
+    }
+}