diff options
-rw-r--r-- | core/res/res/layout/launch_warning.xml | 65 | ||||
-rw-r--r-- | core/res/res/values/strings.xml | 6 | ||||
-rw-r--r-- | core/res/res/values/themes.xml | 7 | ||||
-rw-r--r-- | services/java/com/android/server/am/ActivityManagerService.java | 25 | ||||
-rw-r--r-- | services/java/com/android/server/am/ActivityRecord.java | 25 | ||||
-rw-r--r-- | services/java/com/android/server/am/ActivityStack.java | 46 | ||||
-rw-r--r-- | services/java/com/android/server/am/LaunchWarningWindow.java | 36 |
7 files changed, 196 insertions, 14 deletions
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 3142ee4..483941d 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -752,6 +752,7 @@ public final class ActivityManagerService extends ActivityManagerNative boolean mWaitingUpdate = false; boolean mDidUpdate = false; boolean mOnBattery = false; + boolean mLaunchWarningShown = false; Context mContext; @@ -2902,6 +2903,30 @@ public final class ActivityManagerService extends ActivityManagerNative } } + 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.os.SystemClock; 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 @@ class ActivityRecord extends IApplicationToken.Stub { 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 @@ class ActivityRecord extends IApplicationToken.Stub { 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 @@ class ActivityRecord extends IApplicationToken.Stub { 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 @@ class ActivityRecord extends IApplicationToken.Stub { 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 @@ public class ActivityStack { // 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 @@ public class ActivityStack { 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 @@ public class ActivityStack { 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 @@ public class ActivityStack { 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 @@ public class ActivityStack { 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 @@ public class ActivityStack { 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())); + } +} |