diff options
| author | 2014-05-21 16:06:19 -0400 | |
|---|---|---|
| committer | 2014-06-20 16:17:38 -0400 | |
| commit | 62515beee67307d8859beec521b7baedfa54b2b5 (patch) | |
| tree | e08c69d802fb9929a223c626fe33662a47350085 | |
| parent | 65dfc83cef6dd936deef428f1d318d10ff1d7af5 (diff) | |
Add lock-to-app mode
Added a dialog that shows when app has not been authorized by
DevicePolicyManager.isLockTaskAuthorized. This allows any app
to trigger lock-to-app mode. This same dialog is used when
startLockTaskOnCurrent is triggered by the recents long-press.
Can exit the mode by long-pressing recents again.
Keyguard is disabled when lock-to-app is active.
This CL also prevents apps from finishing when they are the root
task in a lock task TaskRecord.
Change-Id: Ib54d858e570cccf6bfd986958868e15f49bcef75
29 files changed, 641 insertions, 37 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 91328832794e..d0d028908bf0 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -30,6 +30,7 @@ import com.android.internal.policy.PolicyManager; import android.annotation.IntDef; import android.annotation.Nullable; +import android.app.admin.DevicePolicyManager; import android.content.ComponentCallbacks2; import android.content.ComponentName; import android.content.ContentResolver; @@ -5935,14 +5936,21 @@ public class Activity extends ContextThemeWrapper } /** - * Put this Activity in a mode where the user is locked to the + * Request to put this Activity in a mode where the user is locked to the * current task. * * This will prevent the user from launching other apps, going to settings, * or reaching the home screen. * - * Lock task mode will only start if the activity has been whitelisted by the - * Device Owner through DevicePolicyManager#setLockTaskComponents. + * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns true + * for this component then the app will go directly into Lock Task mode. The user + * will not be able to exit this mode until {@link Activity#stopLockTask()} is called. + * + * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns false + * then the system will prompt the user with a dialog requesting permission to enter + * this mode. When entered through this method the user can exit at any time by + * swiping down twice from the top of the screen. Calling stopLockTask will also + * exit the mode. */ public void startLockTask() { try { diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 56462ae73680..572d389651d4 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -2145,6 +2145,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case START_LOCK_TASK_BY_CURRENT: { + data.enforceInterface(IActivityManager.descriptor); + startLockTaskModeOnCurrent(); + reply.writeNoException(); + return true; + } + case STOP_LOCK_TASK_MODE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); stopLockTaskMode(); @@ -2152,6 +2159,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case STOP_LOCK_TASK_BY_CURRENT: { + data.enforceInterface(IActivityManager.descriptor); + stopLockTaskModeOnCurrent(); + reply.writeNoException(); + return true; + } + case IS_IN_LOCK_TASK_MODE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); final boolean isInLockTaskMode = isInLockTaskMode(); @@ -4947,6 +4961,17 @@ class ActivityManagerProxy implements IActivityManager } @Override + public void startLockTaskModeOnCurrent() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(START_LOCK_TASK_BY_CURRENT, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + @Override public void stopLockTaskMode() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -4958,6 +4983,17 @@ class ActivityManagerProxy implements IActivityManager } @Override + public void stopLockTaskModeOnCurrent() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(STOP_LOCK_TASK_BY_CURRENT, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + @Override public boolean isInLockTaskMode() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index bf2d7e5969ba..b630278d2318 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -429,6 +429,9 @@ public interface IActivityManager extends IInterface { public IBinder getHomeActivityToken() throws RemoteException; /** @hide */ + public void startLockTaskModeOnCurrent() throws RemoteException; + + /** @hide */ public void startLockTaskMode(int taskId) throws RemoteException; /** @hide */ @@ -438,6 +441,9 @@ public interface IActivityManager extends IInterface { public void stopLockTaskMode() throws RemoteException; /** @hide */ + public void stopLockTaskModeOnCurrent() throws RemoteException; + + /** @hide */ public boolean isInLockTaskMode() throws RemoteException; /** @hide */ @@ -744,4 +750,6 @@ public interface IActivityManager extends IInterface { int START_VOICE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+218; int GET_ACTIVITY_OPTIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+219; int GET_APP_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+220; + int START_LOCK_TASK_BY_CURRENT = IBinder.FIRST_CALL_TRANSACTION+221; + int STOP_LOCK_TASK_BY_CURRENT = IBinder.FIRST_CALL_TRANSACTION+222; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 06c05eedc8ab..733bdadacf1c 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2460,6 +2460,18 @@ public final class Settings { public static final String POINTER_SPEED = "pointer_speed"; /** + * Whether lock-to-app will be triggered by long-press on recents. + * @hide + */ + public static final String LOCK_TO_APP_ENABLED = "lock_to_app_enabled"; + + /** + * Whether lock-to-app will lock the keyguard when exiting. + * @hide + */ + public static final String LOCK_TO_APP_EXIT_LOCKED = "lock_to_app_exit_locked"; + + /** * I am the lolrus. * <p> * Nonzero values indicate that the user has a bukkit. diff --git a/core/res/res/drawable-hdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-hdpi/ic_lock_open_wht_24dp.png Binary files differnew file mode 100644 index 000000000000..4d97045cbdcd --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_lock_open_wht_24dp.png diff --git a/core/res/res/drawable-hdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-hdpi/ic_lock_outline_wht_24dp.png Binary files differnew file mode 100644 index 000000000000..46fb463ec4ab --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_lock_outline_wht_24dp.png diff --git a/core/res/res/drawable-hdpi/ic_recent.png b/core/res/res/drawable-hdpi/ic_recent.png Binary files differnew file mode 100644 index 000000000000..8866539c5d77 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_recent.png diff --git a/core/res/res/drawable-mdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-mdpi/ic_lock_open_wht_24dp.png Binary files differnew file mode 100644 index 000000000000..163f4a0b48a7 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_lock_open_wht_24dp.png diff --git a/core/res/res/drawable-mdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-mdpi/ic_lock_outline_wht_24dp.png Binary files differnew file mode 100644 index 000000000000..bbfb83c7f1e1 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_lock_outline_wht_24dp.png diff --git a/core/res/res/drawable-mdpi/ic_recent.png b/core/res/res/drawable-mdpi/ic_recent.png Binary files differnew file mode 100644 index 000000000000..2b607df07c97 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_recent.png diff --git a/core/res/res/drawable-xhdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-xhdpi/ic_lock_open_wht_24dp.png Binary files differnew file mode 100644 index 000000000000..21d4d5355168 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_lock_open_wht_24dp.png diff --git a/core/res/res/drawable-xhdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-xhdpi/ic_lock_outline_wht_24dp.png Binary files differnew file mode 100644 index 000000000000..2aeb9a2da0bb --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_lock_outline_wht_24dp.png diff --git a/core/res/res/drawable-xhdpi/ic_recent.png b/core/res/res/drawable-xhdpi/ic_recent.png Binary files differnew file mode 100644 index 000000000000..86316dbb84ef --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_recent.png diff --git a/core/res/res/drawable-xxhdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-xxhdpi/ic_lock_open_wht_24dp.png Binary files differnew file mode 100644 index 000000000000..1b11b597247d --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_lock_open_wht_24dp.png diff --git a/core/res/res/drawable-xxhdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-xxhdpi/ic_lock_outline_wht_24dp.png Binary files differnew file mode 100644 index 000000000000..ae0d655d0d46 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_lock_outline_wht_24dp.png diff --git a/core/res/res/drawable-xxhdpi/ic_recent.png b/core/res/res/drawable-xxhdpi/ic_recent.png Binary files differnew file mode 100644 index 000000000000..e6bd1256bff7 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_recent.png diff --git a/core/res/res/drawable-xxxhdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-xxxhdpi/ic_lock_open_wht_24dp.png Binary files differnew file mode 100644 index 000000000000..877441207c8e --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/ic_lock_open_wht_24dp.png diff --git a/core/res/res/drawable-xxxhdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-xxxhdpi/ic_lock_outline_wht_24dp.png Binary files differnew file mode 100644 index 000000000000..1375accd5c9c --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/ic_lock_outline_wht_24dp.png diff --git a/core/res/res/drawable/lock_task_notify_bg.xml b/core/res/res/drawable/lock_task_notify_bg.xml new file mode 100644 index 000000000000..3a8fab51900d --- /dev/null +++ b/core/res/res/drawable/lock_task_notify_bg.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 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. +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle" > + + <solid android:color="@android:color/black" /> + <corners + android:radius="8dip" /> +</shape> diff --git a/core/res/res/layout/lock_to_app_enter.xml b/core/res/res/layout/lock_to_app_enter.xml new file mode 100644 index 000000000000..c034536f27f0 --- /dev/null +++ b/core/res/res/layout/lock_to_app_enter.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 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. +--> +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingLeft="20dp" + android:paddingRight="20dp" + android:paddingBottom="12dp" + android:alpha=".8" + android:background="@drawable/lock_task_notify_bg" > + + <ImageView + android:id="@+id/lock_icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_centerHorizontal="true" + android:layout_marginTop="20dp" + android:alpha=".8" + android:src="@drawable/ic_lock_outline_wht_24dp" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/lock_icon" + android:layout_centerHorizontal="true" + android:layout_marginTop="2dp" + android:textColor="@android:color/white" + android:alpha=".8" + android:text="@string/lock_to_app_start" /> +</RelativeLayout> diff --git a/core/res/res/layout/lock_to_app_exit.xml b/core/res/res/layout/lock_to_app_exit.xml new file mode 100644 index 000000000000..4a60c80e9b13 --- /dev/null +++ b/core/res/res/layout/lock_to_app_exit.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 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. +--> +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingLeft="20dp" + android:paddingRight="20dp" + android:paddingBottom="12dp" + android:alpha=".8" + android:background="@drawable/lock_task_notify_bg" > + + <ImageView + android:id="@+id/lock_icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_centerHorizontal="true" + android:layout_marginTop="20dp" + android:alpha=".8" + android:src="@drawable/ic_lock_open_wht_24dp" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/lock_icon" + android:layout_centerHorizontal="true" + android:layout_marginTop="2dp" + android:textColor="@android:color/white" + android:alpha=".8" + android:text="@string/lock_to_app_exit" /> +</RelativeLayout> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index c8c0d236bdd6..e017f53e8880 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4746,4 +4746,17 @@ <!-- DO NOT TRANSLATE --> <string name="day_of_week_label_typeface">sans-serif</string> + <!-- Lock-to-app dialog title. --> + <string name="lock_to_app_title">Use lock-to-app?</string> + <!-- Lock-to-app dialog description. The $ is not actually shown or translated, it is a marker of where the recents icon shows up. --> + <string name="lock_to_app_description">Lock-to-app locks the display in a single app.\n\nTo exit press and hold the recent apps button $</string> + <!-- Lock-to-app negative response. --> + <string name="lock_to_app_negative">NO</string> + <!-- Lock-to-app positive response. --> + <string name="lock_to_app_positive">START</string> + <!-- Starting lock-to-app indication. --> + <string name="lock_to_app_start">Start Lock-to-app</string> + <!-- Exting lock-to-app indication. --> + <string name="lock_to_app_exit">Exit Lock-to-app</string> + </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 1547bbdcfb3f..7d0d75972b37 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -604,6 +604,14 @@ <java-symbol type="string" name="kilobyteShort" /> <java-symbol type="string" name="last_month" /> <java-symbol type="string" name="launchBrowserDefault" /> + <java-symbol type="string" name="lock_to_app_title" /> + <java-symbol type="string" name="lock_to_app_description" /> + <java-symbol type="string" name="lock_to_app_negative" /> + <java-symbol type="string" name="lock_to_app_positive" /> + <java-symbol type="drawable" name="ic_recent" /> + <java-symbol type="layout" name="lock_to_app_enter" /> + <java-symbol type="layout" name="lock_to_app_exit" /> + <java-symbol type="drawable" name="lock_task_notify_bg" /> <java-symbol type="string" name="lockscreen_access_pattern_cell_added" /> <java-symbol type="string" name="lockscreen_access_pattern_cleared" /> <java-symbol type="string" name="lockscreen_access_pattern_detected" /> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 1da7dabb55be..f57b79726c18 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -35,6 +35,7 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.app.ActivityManager; import android.app.ActivityManagerNative; +import android.app.IActivityManager; import android.app.Notification; import android.app.PendingIntent; import android.app.StatusBarManager; @@ -64,6 +65,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Global; +import android.provider.Settings.SettingNotFoundException; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.util.ArraySet; @@ -893,6 +895,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } }; + private View.OnLongClickListener mLockToAppClickListener = new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + toggleLockedApp(); + return true; + } + }; + private int mShowSearchHoldoff = 0; private Runnable mShowSearchPanel = new Runnable() { public void run() { @@ -936,6 +946,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener); mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener); + mNavigationBarView.getRecentsButton().setLongClickable(true); + mNavigationBarView.getRecentsButton().setOnLongClickListener(mLockToAppClickListener); mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener); updateSearchPanel(); } @@ -3133,6 +3145,28 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mStackScroller.setAnimationsEnabled(true); } + public void toggleLockedApp() { + Log.d(TAG, "Trying to toggle lock-to-app"); + try { + IActivityManager activityManager = ActivityManagerNative.getDefault(); + if (activityManager.isInLockTaskMode()) { + activityManager.stopLockTaskModeOnCurrent(); + } else { + try { + boolean lockToAppEnabled = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.LOCK_TO_APP_ENABLED) != 0; + if (lockToAppEnabled) { + activityManager.startLockTaskModeOnCurrent(); + } + } catch (SettingNotFoundException e) { + // No setting, not enabled. + } + } + } catch (RemoteException e) { + Log.d(TAG, "Unable to toggle Lock-to-app", e); + } + } + private final Runnable mUserActivity = new Runnable() { @Override public void run() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index 330b599fe3b9..0134fe85b1bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -63,6 +63,7 @@ public class KeyButtonView extends ImageView { // Just an old-fashioned ImageView performLongClick(); } + setPressed(false); } } }; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 34c1ecd7b72f..5dd1a14a9cea 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -36,6 +36,9 @@ import android.app.IActivityContainerCallback; import android.app.IAppTask; import android.app.admin.DevicePolicyManager; import android.appwidget.AppWidgetManager; +import android.content.DialogInterface.OnClickListener; +import android.content.res.Resources; +import android.graphics.BitmapFactory; import android.graphics.Rect; import android.os.BatteryStats; import android.os.PersistableBundle; @@ -171,8 +174,12 @@ import android.os.SystemProperties; import android.os.UpdateLock; import android.os.UserHandle; import android.provider.Settings; +import android.text.Spannable; +import android.text.SpannableString; import android.text.format.DateUtils; import android.text.format.Time; +import android.text.style.DynamicDrawableSpan; +import android.text.style.ImageSpan; import android.util.AtomicFile; import android.util.EventLog; import android.util.Log; @@ -186,6 +193,7 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; +import android.widget.TextView; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -1158,6 +1166,8 @@ public final class ActivityManagerService extends ActivityManagerNative CompatModeDialog mCompatModeDialog; long mLastMemUsageReportTime = 0; + private LockToAppRequestDialog mLockToAppRequest; + /** * Flag whether the current user is a "monkey", i.e. whether * the UI is driven by a UI automation tool. @@ -2176,6 +2186,8 @@ public final class ActivityManagerService extends ActivityManagerNative } }; + mLockToAppRequest = new LockToAppRequestDialog(mContext, this); + Watchdog.getInstance().addMonitor(this); Watchdog.getInstance().addThread(mHandler); } @@ -3662,6 +3674,12 @@ public final class ActivityManagerService extends ActivityManagerNative // Keep track of the root activity of the task before we finish it TaskRecord tr = r.task; ActivityRecord rootR = tr.getRootActivity(); + // Do not allow task to finish in Lock Task mode. + if (tr == mStackSupervisor.mLockTaskModeTask) { + if (rootR == r) { + return false; + } + } if (mController != null) { // Find the first activity that is not finishing. ActivityRecord next = r.task.stack.topRunningActivityLocked(token, 0); @@ -3806,13 +3824,25 @@ public final class ActivityManagerService extends ActivityManagerNative public boolean finishActivityAffinity(IBinder token) { synchronized(this) { final long origId = Binder.clearCallingIdentity(); - ActivityRecord r = ActivityRecord.isInStackLocked(token); - boolean res = false; - if (r != null) { - res = r.task.stack.finishActivityAffinityLocked(r); + try { + ActivityRecord r = ActivityRecord.isInStackLocked(token); + + ActivityRecord rootR = r.task.getRootActivity(); + // Do not allow task to finish in Lock Task mode. + if (r.task == mStackSupervisor.mLockTaskModeTask) { + if (rootR == r) { + Binder.restoreCallingIdentity(origId); + return false; + } + } + boolean res = false; + if (r != null) { + res = r.task.stack.finishActivityAffinityLocked(r); + } + return res; + } finally { + Binder.restoreCallingIdentity(origId); } - Binder.restoreCallingIdentity(origId); - return res; } } @@ -7642,12 +7672,20 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private void startLockTaskMode(TaskRecord task) { + void startLockTaskMode(TaskRecord task) { final String pkg; synchronized (this) { pkg = task.intent.getComponent().getPackageName(); } - if (!isLockTaskAuthorized(pkg)) { + boolean isSystemInitiated = Binder.getCallingUid() == Process.SYSTEM_UID; + if (!isSystemInitiated && !isLockTaskAuthorized(pkg)) { + final TaskRecord taskRecord = task; + mHandler.post(new Runnable() { + @Override + public void run() { + mLockToAppRequest.showLockTaskPrompt(taskRecord); + } + }); return; } long ident = Binder.clearCallingIdentity(); @@ -7659,7 +7697,7 @@ public final class ActivityManagerService extends ActivityManagerNative if ((mFocusedActivity == null) || (task != mFocusedActivity.task)) { throw new IllegalArgumentException("Invalid task, not in foreground"); } - mStackSupervisor.setLockTaskModeLocked(task); + mStackSupervisor.setLockTaskModeLocked(task, isSystemInitiated); } } } finally { @@ -7704,24 +7742,55 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override + public void startLockTaskModeOnCurrent() throws RemoteException { + checkCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS); + ActivityRecord r = null; + synchronized (this) { + r = mStackSupervisor.topRunningActivityLocked(); + } + startLockTaskMode(r.task); + } + + @Override public void stopLockTaskMode() { // Verify that the user matches the package of the intent for the TaskRecord - // we are locked to. This will ensure the same caller for startLockTaskMode and - // stopLockTaskMode. + // we are locked to or systtem. This will ensure the same caller for startLockTaskMode + // and stopLockTaskMode. + final int callingUid = Binder.getCallingUid(); + if (callingUid != Process.SYSTEM_UID) { + try { + String pkg = + mStackSupervisor.mLockTaskModeTask.intent.getComponent().getPackageName(); + int uid = mContext.getPackageManager().getPackageUid(pkg, + Binder.getCallingUserHandle().getIdentifier()); + if (uid != callingUid) { + throw new SecurityException("Invalid uid, expected " + uid); + } + } catch (NameNotFoundException e) { + Log.d(TAG, "stopLockTaskMode " + e); + return; + } + } + long ident = Binder.clearCallingIdentity(); try { - String pkg = mStackSupervisor.mLockTaskModeTask.intent.getPackage(); - int uid = mContext.getPackageManager().getPackageUid(pkg, - Binder.getCallingUserHandle().getIdentifier()); - if (uid != Binder.getCallingUid()) { - throw new SecurityException("Invalid uid, expected " + uid); + Log.d(TAG, "stopLockTaskMode"); + // Stop lock task + synchronized (this) { + mStackSupervisor.setLockTaskModeLocked(null, false); } - } catch (NameNotFoundException e) { - Log.d(TAG, "stopLockTaskMode " + e); - return; + } finally { + Binder.restoreCallingIdentity(ident); } - // Stop lock task - synchronized (this) { - mStackSupervisor.setLockTaskModeLocked(null); + } + + @Override + public void stopLockTaskModeOnCurrent() throws RemoteException { + checkCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS); + long ident = Binder.clearCallingIdentity(); + try { + stopLockTaskMode(); + } finally { + Binder.restoreCallingIdentity(ident); } } @@ -16651,7 +16720,7 @@ public final class ActivityManagerService extends ActivityManagerNative return true; } - mStackSupervisor.setLockTaskModeLocked(null); + mStackSupervisor.setLockTaskModeLocked(null, false); final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId); if (userInfo == null) { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 9264186c6329..6f2996c73501 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -79,6 +79,8 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; +import android.provider.Settings; +import android.provider.Settings.SettingNotFoundException; import android.service.voice.IVoiceInteractionSession; import android.util.EventLog; import android.util.Slog; @@ -142,6 +144,8 @@ public final class ActivityStackSupervisor implements DisplayListener { private final static String VIRTUAL_DISPLAY_BASE_NAME = "ActivityViewVirtualDisplay"; + private static final String LOCK_TASK_TAG = "Lock-to-App"; + /** Status Bar Service **/ private IBinder mToken = new Binder(); private IStatusBarService mStatusBarService; @@ -255,6 +259,10 @@ public final class ActivityStackSupervisor implements DisplayListener { /** If non-null then the task specified remains in front and no other tasks may be started * until the task exits or #stopLockTaskMode() is called. */ TaskRecord mLockTaskModeTask; + /** + * Notifies the user when entering/exiting lock-task. + */ + private LockTaskNotify mLockTaskNotify; public ActivityStackSupervisor(ActivityManagerService service) { mService = service; @@ -3004,7 +3012,7 @@ public final class ActivityStackSupervisor implements DisplayListener { return list; } - void setLockTaskModeLocked(TaskRecord task) { + void setLockTaskModeLocked(TaskRecord task, boolean showHomeRecents) { if (task == null) { // Take out of lock task mode if necessary if (mLockTaskModeTask != null) { @@ -3028,6 +3036,7 @@ public final class ActivityStackSupervisor implements DisplayListener { lockTaskMsg.obj = mLockTaskModeTask.intent.getComponent().getPackageName(); lockTaskMsg.arg1 = mLockTaskModeTask.userId; lockTaskMsg.what = LOCK_TASK_START_MSG; + lockTaskMsg.arg2 = showHomeRecents ? 1 : 0; mHandler.sendMessage(lockTaskMsg); } @@ -3130,15 +3139,24 @@ public final class ActivityStackSupervisor implements DisplayListener { case LOCK_TASK_START_MSG: { // When lock task starts, we disable the status bars. try { + if (mLockTaskNotify == null) { + mLockTaskNotify = new LockTaskNotify(mService.mContext); + } + mLockTaskNotify.show(true); if (getStatusBarService() != null) { - getStatusBarService().disable - (StatusBarManager.DISABLE_MASK ^ StatusBarManager.DISABLE_BACK, - mToken, mService.mContext.getPackageName()); + int flags = + StatusBarManager.DISABLE_MASK ^ StatusBarManager.DISABLE_BACK; + if (msg.arg2 != 0) { + flags ^= StatusBarManager.DISABLE_HOME + | StatusBarManager.DISABLE_RECENT; + } + getStatusBarService().disable(flags, mToken, + mService.mContext.getPackageName()); } + mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG); if (getDevicePolicyManager() != null) { getDevicePolicyManager().notifyLockTaskModeChanged(true, - (String)msg.obj, - msg.arg1); + (String)msg.obj, msg.arg1); } } catch (RemoteException ex) { throw new RuntimeException(ex); @@ -3147,15 +3165,29 @@ public final class ActivityStackSupervisor implements DisplayListener { case LOCK_TASK_END_MSG: { // When lock task ends, we enable the status bars. try { - if (getStatusBarService() != null) { - getStatusBarService().disable - (StatusBarManager.DISABLE_NONE, - mToken, mService.mContext.getPackageName()); - } + if (getStatusBarService() != null) { + getStatusBarService().disable(StatusBarManager.DISABLE_NONE, mToken, + mService.mContext.getPackageName()); + } + mWindowManager.reenableKeyguard(mToken); if (getDevicePolicyManager() != null) { getDevicePolicyManager().notifyLockTaskModeChanged(false, null, msg.arg1); } + if (mLockTaskNotify == null) { + mLockTaskNotify = new LockTaskNotify(mService.mContext); + } + mLockTaskNotify.show(false); + try { + boolean shouldLockKeyguard = Settings.System.getInt( + mService.mContext.getContentResolver(), + Settings.System.LOCK_TO_APP_EXIT_LOCKED) != 0; + if (shouldLockKeyguard) { + mWindowManager.lockNow(null); + } + } catch (SettingNotFoundException e) { + // No setting, don't lock. + } } catch (RemoteException ex) { throw new RuntimeException(ex); } diff --git a/services/core/java/com/android/server/am/LockTaskNotify.java b/services/core/java/com/android/server/am/LockTaskNotify.java new file mode 100644 index 000000000000..1997f4619a21 --- /dev/null +++ b/services/core/java/com/android/server/am/LockTaskNotify.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2013 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.server.am; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.PixelFormat; +import android.os.Handler; +import android.os.Message; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; + +import com.android.internal.R; + +/** + * Helper to manage showing/hiding a image to notify them that they are entering + * or exiting lock-to-app mode. + */ +public class LockTaskNotify { + private static final String TAG = "LockTaskNotify"; + + private static final int SHOW_LENGTH_MS = 1500; + + private final Context mContext; + private final H mHandler; + + private ClingWindowView mClingWindow; + private WindowManager mWindowManager; + private boolean mIsStarting; + + public LockTaskNotify(Context context) { + mContext = context; + mHandler = new H(); + mWindowManager = (WindowManager) + mContext.getSystemService(Context.WINDOW_SERVICE); + } + + public void show(boolean starting) { + mIsStarting = starting; + mHandler.obtainMessage(H.SHOW).sendToTarget(); + } + + public WindowManager.LayoutParams getClingWindowLayoutParams() { + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_TOAST, + 0 + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + , + PixelFormat.TRANSLUCENT); + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; + lp.setTitle("LockTaskNotify"); + lp.windowAnimations = com.android.internal.R.style.Animation_RecentApplications; + lp.gravity = Gravity.FILL; + return lp; + } + + public FrameLayout.LayoutParams getImageLayoutParams() { + return new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL); + } + + private void handleShow() { + mClingWindow = new ClingWindowView(mContext); + + // we will be hiding the nav bar, so layout as if it's already hidden + mClingWindow.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + + // show the confirmation + WindowManager.LayoutParams lp = getClingWindowLayoutParams(); + mWindowManager.addView(mClingWindow, lp); + } + + private void handleHide() { + if (mClingWindow != null) { + mWindowManager.removeView(mClingWindow); + mClingWindow = null; + } + } + + + private class ClingWindowView extends FrameLayout { + private View mView; + + private Runnable mUpdateLayoutRunnable = new Runnable() { + @Override + public void run() { + if (mView != null && mView.getParent() != null) { + mView.setLayoutParams(getImageLayoutParams()); + } + } + }; + + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) { + post(mUpdateLayoutRunnable); + } + } + }; + + public ClingWindowView(Context context) { + super(context); + setClickable(true); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + DisplayMetrics metrics = new DisplayMetrics(); + mWindowManager.getDefaultDisplay().getMetrics(metrics); + + int id = R.layout.lock_to_app_exit; + if (mIsStarting) { + id = R.layout.lock_to_app_enter; + } + mView = View.inflate(getContext(), id, null); + + addView(mView, getImageLayoutParams()); + + mContext.registerReceiver(mReceiver, + new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)); + mHandler.sendMessageDelayed(mHandler.obtainMessage(H.HIDE), SHOW_LENGTH_MS); + } + + @Override + public void onDetachedFromWindow() { + mContext.unregisterReceiver(mReceiver); + } + + @Override + public boolean onTouchEvent(MotionEvent motion) { + Slog.v(TAG, "ClingWindowView.onTouchEvent"); + return true; + } + } + + private final class H extends Handler { + private static final int SHOW = 1; + private static final int HIDE = 2; + + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case SHOW: + handleShow(); + break; + case HIDE: + handleHide(); + break; + } + } + } +} diff --git a/services/core/java/com/android/server/am/LockToAppRequestDialog.java b/services/core/java/com/android/server/am/LockToAppRequestDialog.java new file mode 100644 index 000000000000..6e86dff1c54a --- /dev/null +++ b/services/core/java/com/android/server/am/LockToAppRequestDialog.java @@ -0,0 +1,87 @@ + +package com.android.server.am; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.res.Resources; +import android.graphics.BitmapFactory; +import android.provider.Settings; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.DynamicDrawableSpan; +import android.text.style.ImageSpan; +import android.util.Slog; +import android.view.WindowManager; +import android.widget.TextView; + +import com.android.internal.R; + +public class LockToAppRequestDialog implements OnClickListener { + private static final String TAG = "ActivityManager"; + + final private Context mContext; + final private ActivityManagerService mService; + + private AlertDialog mDialog; + private TaskRecord mRequestedTask; + + public LockToAppRequestDialog(Context context, ActivityManagerService activityManagerService) { + mContext = context; + mService = activityManagerService; + } + + public void showLockTaskPrompt(TaskRecord task) { + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + } + mRequestedTask = task; + + final Resources r = Resources.getSystem(); + final String descriptionString = r.getString(R.string.lock_to_app_description); + final SpannableString description = + new SpannableString(descriptionString.replace('$', ' ')); + final ImageSpan imageSpan = new ImageSpan(mContext, + BitmapFactory.decodeResource(r, R.drawable.ic_recent), + DynamicDrawableSpan.ALIGN_BOTTOM); + final int index = descriptionString.indexOf('$'); + if (index >= 0) { + description.setSpan(imageSpan, index, index + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + mDialog = + new AlertDialog.Builder(mContext) + .setTitle(r.getString(R.string.lock_to_app_title)) + .setMessage(description) + .setPositiveButton(r.getString(R.string.lock_to_app_positive), this) + .setNegativeButton(r.getString(R.string.lock_to_app_negative), this) + .create(); + + mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + mDialog.show(); + + // Make icon fit. + final TextView msgTxt = (TextView) mDialog.findViewById(R.id.message); + final float width = imageSpan.getDrawable().getIntrinsicWidth(); + final float height = imageSpan.getDrawable().getIntrinsicHeight(); + final int lineHeight = msgTxt.getLineHeight(); + imageSpan.getDrawable().setBounds(0, 0, (int) (lineHeight * width / height), lineHeight); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (DialogInterface.BUTTON_POSITIVE == which) { + Slog.d(TAG, "accept lock-to-app request"); + // Automatically enable if not currently on. (Could be triggered by an app) + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.LOCK_TO_APP_ENABLED, 1); + + // Start lock-to-app. + mService.startLockTaskMode(mRequestedTask); + } else { + Slog.d(TAG, "ignore lock-to-app request"); + } + } + +} |