Merge "Improve shadow tessellation performance"
diff --git a/api/current.txt b/api/current.txt
index 62154b8..0a0f148 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -825,6 +825,7 @@
field public static final int persistent = 16842765; // 0x101000d
field public static final int persistentDrawingCache = 16842990; // 0x10100ee
field public static final deprecated int phoneNumber = 16843111; // 0x1010167
+ field public static final int pinned = 16843776; // 0x1010400
field public static final int pivotX = 16843189; // 0x10101b5
field public static final int pivotY = 16843190; // 0x10101b6
field public static final int popupAnimationStyle = 16843465; // 0x10102c9
@@ -1294,6 +1295,8 @@
field public static final int dialog_min_width_minor = 17104900; // 0x1050004
field public static final int notification_large_icon_height = 17104902; // 0x1050006
field public static final int notification_large_icon_width = 17104901; // 0x1050005
+ field public static final int recents_thumbnail_height = 17104903; // 0x1050007
+ field public static final int recents_thumbnail_width = 17104904; // 0x1050008
field public static final int thumbnail_height = 17104897; // 0x1050001
field public static final int thumbnail_width = 17104898; // 0x1050002
}
@@ -2196,6 +2199,8 @@
field public static final int Widget_Quantum_Button_Borderless = 16974393; // 0x1030239
field public static final int Widget_Quantum_Button_Borderless_Small = 16974394; // 0x103023a
field public static final int Widget_Quantum_Button_Inset = 16974395; // 0x103023b
+ field public static final int Widget_Quantum_Button_Paper = 16974503; // 0x10302a7
+ field public static final int Widget_Quantum_Button_Paper_Color = 16974504; // 0x10302a8
field public static final int Widget_Quantum_Button_Small = 16974396; // 0x103023c
field public static final int Widget_Quantum_Button_Toggle = 16974397; // 0x103023d
field public static final int Widget_Quantum_CalendarView = 16974398; // 0x103023e
@@ -2232,6 +2237,8 @@
field public static final int Widget_Quantum_Light_Button = 16974452; // 0x1030274
field public static final int Widget_Quantum_Light_Button_Borderless_Small = 16974453; // 0x1030275
field public static final int Widget_Quantum_Light_Button_Inset = 16974454; // 0x1030276
+ field public static final int Widget_Quantum_Light_Button_Paper = 16974505; // 0x10302a9
+ field public static final int Widget_Quantum_Light_Button_Paper_Color = 16974506; // 0x10302aa
field public static final int Widget_Quantum_Light_Button_Small = 16974455; // 0x1030277
field public static final int Widget_Quantum_Light_Button_Toggle = 16974456; // 0x1030278
field public static final int Widget_Quantum_Light_CalendarView = 16974457; // 0x1030279
@@ -4736,7 +4743,7 @@
field public static final java.lang.String ACTION_PASSWORD_EXPIRING = "android.app.action.ACTION_PASSWORD_EXPIRING";
field public static final java.lang.String ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED";
field public static final java.lang.String ACTION_PASSWORD_SUCCEEDED = "android.app.action.ACTION_PASSWORD_SUCCEEDED";
- field public static final java.lang.String ACTION_PROFILE_PROVISIONING_COMPLETE = "android.managedprovisioning.ACTION_PROVISIONING_COMPLETE";
+ field public static final java.lang.String ACTION_PROFILE_PROVISIONING_COMPLETE = "android.app.action.ACTION_PROFILE_PROVISIONING_COMPLETE";
field public static final java.lang.String DEVICE_ADMIN_META_DATA = "android.app.device_admin";
field public static final java.lang.String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING";
}
@@ -4789,6 +4796,7 @@
method public int setStorageEncryption(android.content.ComponentName, boolean);
method public void wipeData(int);
field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
+ field public static final java.lang.String ACTION_PROVISION_MANAGED_PROFILE = "android.managedprovisioning.ACTION_PROVISION_MANAGED_PROFILE";
field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2
@@ -4797,6 +4805,8 @@
field public static final int ENCRYPTION_STATUS_UNSUPPORTED = 0; // 0x0
field public static final java.lang.String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
field public static final java.lang.String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
+ field public static final java.lang.String EXTRA_PROVISIONING_DEFAULT_MANAGED_PROFILE_NAME = "defaultManagedProfileName";
+ field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME = "deviceAdminPackageName";
field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
field public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 2; // 0x2
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index c877cd3..7f7616f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -933,6 +933,16 @@
}
}
+ /** @hide */
+ public boolean isInHomeStack(int taskId) {
+ try {
+ return ActivityManagerNative.getDefault().isInHomeStack(taskId);
+ } catch (RemoteException e) {
+ // System dead, we will be dead too soon!
+ return false;
+ }
+ }
+
/**
* Flag for {@link #moveTaskToFront(int, int)}: also move the "home"
* activity along with the task, so it is positioned immediately behind
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index f4358e9..c7c81dd 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -654,6 +654,15 @@
return true;
}
+ case IS_IN_HOME_STACK_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int taskId = data.readInt();
+ boolean isInHomeStack = isInHomeStack(taskId);
+ reply.writeNoException();
+ reply.writeInt(isInHomeStack ? 1 : 0);
+ return true;
+ }
+
case SET_FOCUSED_STACK_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int stackId = data.readInt();
@@ -2833,6 +2842,19 @@
return info;
}
@Override
+ public boolean isInHomeStack(int taskId) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(taskId);
+ mRemote.transact(IS_IN_HOME_STACK_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean isInHomeStack = reply.readInt() > 0;
+ data.recycle();
+ reply.recycle();
+ return isInHomeStack;
+ }
+ @Override
public void setFocusedStack(int stackId) throws RemoteException
{
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 138eea2..9cfd85a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3017,11 +3017,17 @@
int h;
if (w < 0) {
Resources res = r.activity.getResources();
- mThumbnailHeight = h =
- res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
-
- mThumbnailWidth = w =
- res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
+ if (SystemProperties.getBoolean("persist.recents.use_alternate", false)) {
+ int wId = com.android.internal.R.dimen.recents_thumbnail_width;
+ int hId = com.android.internal.R.dimen.recents_thumbnail_height;
+ mThumbnailWidth = w = res.getDimensionPixelSize(wId);
+ mThumbnailHeight = h = res.getDimensionPixelSize(hId);
+ } else {
+ mThumbnailHeight = h =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
+ mThumbnailWidth = w =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
+ }
} else {
h = mThumbnailHeight;
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 1943bba..f2cabf4 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -122,6 +122,7 @@
public void resizeStack(int stackId, Rect bounds) throws RemoteException;
public List<StackInfo> getAllStackInfos() throws RemoteException;
public StackInfo getStackInfo(int stackId) throws RemoteException;
+ public boolean isInHomeStack(int taskId) throws RemoteException;
public void setFocusedStack(int stackId) throws RemoteException;
public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException;
/* oneway */
@@ -717,4 +718,5 @@
// Start of L transactions
int GET_TAG_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+210;
int START_USER_IN_BACKGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+211;
+ int IS_IN_HOME_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+212;
}
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index 00e7da4..6f4533c 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -164,13 +164,21 @@
public static final String ACTION_PASSWORD_EXPIRING
= "android.app.action.ACTION_PASSWORD_EXPIRING";
-
/**
- * Action broadcasted when provisioning of a managed profile has completed.
+ * Broadcast Action: This broadcast is sent to the newly created profile when
+ * the provisioning of a managed profile has completed successfully.
+ *
+ * <p>The broadcast is limited to the package which started the provisioning as specified in
+ * the extra {@link DevicePolicyManager#EXTRA_PROVISIONING_MDM_PACKAGE_NAME} of the
+ * {@link DevicePolicyManager#ACTION_PROVISION_MANAGED_PROFILE} intent that started the
+ * provisioning. It is also limited to the managed profile.
+ *
+ * <p>Input: Nothing.</p>
+ * <p>Output: Nothing</p>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_PROFILE_PROVISIONING_COMPLETE
- = "android.managedprovisioning.ACTION_PROVISIONING_COMPLETE";
+ public static final String ACTION_PROFILE_PROVISIONING_COMPLETE =
+ "android.app.action.ACTION_PROFILE_PROVISIONING_COMPLETE";
/**
* Name under which a DevicePolicy component publishes information
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 1f41c56..e06cf38 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -77,6 +77,43 @@
}
/**
+ * Activity action: Starts the provisioning flow which sets up a managed profile.
+ * This intent will typically be sent by a mobile device management application(mdm).
+ * Managed profile provisioning creates a profile, moves the mdm to the profile,
+ * sets the mdm as the profile owner and removes all non required applications from the profile.
+ * As a profile owner the mdm than has full control over the managed profile.
+ *
+ * <p>The intent must contain the extras {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} and
+ * {@link #EXTRA_PROVISIONING_DEFAULT_MANAGED_PROFILE_NAME}.
+ *
+ * <p> When managed provisioning has completed, an intent of the type
+ * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcasted to the
+ * mdm app on the managed profile.
+ *
+ * <p>Input: Nothing.</p>
+ * <p>Output: Nothing</p>
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PROVISION_MANAGED_PROFILE
+ = "android.managedprovisioning.ACTION_PROVISION_MANAGED_PROFILE";
+
+ /**
+ * A String extra holding the name of the package of the mobile device management application
+ * that starts the managed provisioning flow. This package will be set as the profile owner.
+ * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}.
+ */
+ public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME
+ = "deviceAdminPackageName";
+
+ /**
+ * A String extra holding the default name of the profile that is created during managed profile
+ * provisioning.
+ * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}
+ */
+ public static final String EXTRA_PROVISIONING_DEFAULT_MANAGED_PROFILE_NAME
+ = "defaultManagedProfileName";
+
+ /**
* Activity action: ask the user to add a new device administrator to the system.
* The desired policy is the ComponentName of the policy in the
* {@link #EXTRA_DEVICE_ADMIN} extra field. This will invoke a UI to
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 9d0ab3a..02c850b 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -987,7 +987,7 @@
* Implement this to handle requests to delete one or more rows.
* The implementation should apply the selection clause when performing
* deletion, allowing the operation to affect multiple rows in a directory.
- * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyDelete()}
+ * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
* after deleting.
* This method can be called from multiple threads, as described in
* <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 8c23129..5f2b5f0 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -1359,7 +1359,7 @@
float q2 = rotationVector[1];
float q3 = rotationVector[2];
- if (rotationVector.length == 4) {
+ if (rotationVector.length >= 4) {
q0 = rotationVector[3];
} else {
q0 = 1 - q1*q1 - q2*q2 - q3*q3;
@@ -1416,7 +1416,7 @@
* @param Q an array of floats in which to store the computed quaternion
*/
public static void getQuaternionFromVector(float[] Q, float[] rv) {
- if (rv.length == 4) {
+ if (rv.length >= 4) {
Q[0] = rv[3];
} else {
Q[0] = 1 - rv[0]*rv[0] - rv[1]*rv[1] - rv[2]*rv[2];
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index f1e7e98..465d142 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -19,6 +19,7 @@
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.KeyboardLayout;
import android.hardware.input.IInputDevicesChangedListener;
+import android.hardware.input.TouchCalibration;
import android.os.IBinder;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -39,6 +40,11 @@
// applications, the caller must have the INJECT_EVENTS permission.
boolean injectInputEvent(in InputEvent ev, int mode);
+ // Calibrate input device position
+ TouchCalibration getTouchCalibrationForInputDevice(String inputDeviceDescriptor, int rotation);
+ void setTouchCalibrationForInputDevice(String inputDeviceDescriptor, int rotation,
+ in TouchCalibration calibration);
+
// Keyboard layouts configuration.
KeyboardLayout[] getKeyboardLayouts();
KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index a2aeafb..e3a3830 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -500,6 +500,45 @@
}
/**
+ * Gets the TouchCalibration applied to the specified input device's coordinates.
+ *
+ * @param inputDeviceDescriptor The input device descriptor.
+ * @return The TouchCalibration currently assigned for use with the given
+ * input device. If none is set, an identity TouchCalibration is returned.
+ *
+ * @hide
+ */
+ public TouchCalibration getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation) {
+ try {
+ return mIm.getTouchCalibrationForInputDevice(inputDeviceDescriptor, surfaceRotation);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not get calibration matrix for input device.", ex);
+ return TouchCalibration.IDENTITY;
+ }
+ }
+
+ /**
+ * Sets the TouchCalibration to apply to the specified input device's coordinates.
+ * <p>
+ * This method may have the side-effect of causing the input device in question
+ * to be reconfigured. Requires {@link android.Manifest.permissions.SET_INPUT_CALIBRATION}.
+ * </p>
+ *
+ * @param inputDeviceDescriptor The input device descriptor.
+ * @param calibration The calibration to be applied
+ *
+ * @hide
+ */
+ public void setTouchCalibration(String inputDeviceDescriptor, int surfaceRotation,
+ TouchCalibration calibration) {
+ try {
+ mIm.setTouchCalibrationForInputDevice(inputDeviceDescriptor, surfaceRotation, calibration);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not set calibration matrix for input device.", ex);
+ }
+ }
+
+ /**
* Gets the mouse pointer speed.
* <p>
* Only returns the permanent mouse pointer speed. Ignores any temporary pointer
diff --git a/core/java/android/hardware/input/TouchCalibration.aidl b/core/java/android/hardware/input/TouchCalibration.aidl
new file mode 100644
index 0000000..2c28774
--- /dev/null
+++ b/core/java/android/hardware/input/TouchCalibration.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+package android.hardware.input;
+
+parcelable TouchCalibration;
diff --git a/core/java/android/hardware/input/TouchCalibration.java b/core/java/android/hardware/input/TouchCalibration.java
new file mode 100644
index 0000000..025fad0
--- /dev/null
+++ b/core/java/android/hardware/input/TouchCalibration.java
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+package android.hardware.input;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Encapsulates calibration data for input devices.
+ *
+ * @hide
+ */
+public class TouchCalibration implements Parcelable {
+
+ public static final TouchCalibration IDENTITY = new TouchCalibration();
+
+ public static final Parcelable.Creator<TouchCalibration> CREATOR
+ = new Parcelable.Creator<TouchCalibration>() {
+ public TouchCalibration createFromParcel(Parcel in) {
+ return new TouchCalibration(in);
+ }
+
+ public TouchCalibration[] newArray(int size) {
+ return new TouchCalibration[size];
+ }
+ };
+
+ private final float mXScale, mXYMix, mXOffset;
+ private final float mYXMix, mYScale, mYOffset;
+
+ /**
+ * Create a new TouchCalibration initialized to the identity transformation.
+ */
+ public TouchCalibration() {
+ this(1,0,0,0,1,0);
+ }
+
+ /**
+ * Create a new TouchCalibration from affine transformation paramters.
+ * @param xScale Influence of input x-axis value on output x-axis value.
+ * @param xyMix Influence of input y-axis value on output x-axis value.
+ * @param xOffset Constant offset to be applied to output x-axis value.
+ * @param yXMix Influence of input x-axis value on output y-axis value.
+ * @param yScale Influence of input y-axis value on output y-axis value.
+ * @param yOffset Constant offset to be applied to output y-axis value.
+ */
+ public TouchCalibration(float xScale, float xyMix, float xOffset,
+ float yxMix, float yScale, float yOffset) {
+ mXScale = xScale;
+ mXYMix = xyMix;
+ mXOffset = xOffset;
+ mYXMix = yxMix;
+ mYScale = yScale;
+ mYOffset = yOffset;
+ }
+
+ public TouchCalibration(Parcel in) {
+ mXScale = in.readFloat();
+ mXYMix = in.readFloat();
+ mXOffset = in.readFloat();
+ mYXMix = in.readFloat();
+ mYScale = in.readFloat();
+ mYOffset = in.readFloat();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(mXScale);
+ dest.writeFloat(mXYMix);
+ dest.writeFloat(mXOffset);
+ dest.writeFloat(mYXMix);
+ dest.writeFloat(mYScale);
+ dest.writeFloat(mYOffset);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public float[] getAffineTransform() {
+ return new float[] { mXScale, mXYMix, mXOffset, mYXMix, mYScale, mYOffset };
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ } else if (obj instanceof TouchCalibration) {
+ TouchCalibration cal = (TouchCalibration)obj;
+
+ return (cal.mXScale == mXScale) &&
+ (cal.mXYMix == mXYMix) &&
+ (cal.mXOffset == mXOffset) &&
+ (cal.mYXMix == mYXMix) &&
+ (cal.mYScale == mYScale) &&
+ (cal.mYOffset == mYOffset);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Float.floatToIntBits(mXScale) ^
+ Float.floatToIntBits(mXYMix) ^
+ Float.floatToIntBits(mXOffset) ^
+ Float.floatToIntBits(mYXMix) ^
+ Float.floatToIntBits(mYScale) ^
+ Float.floatToIntBits(mYOffset);
+ }
+}
diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java
index 9522112..0f8da44 100644
--- a/core/java/android/util/Patterns.java
+++ b/core/java/android/util/Patterns.java
@@ -191,8 +191,6 @@
for (int i = 1; i <= numGroups; i++) {
String s = matcher.group(i);
- System.err.println("Group(" + i + ") : " + s);
-
if (s != null) {
b.append(s);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 904ec44..d72f810 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -10541,7 +10541,7 @@
* by the layout system and should not generally be called otherwise, because the property
* may be changed at any time by the layout.
*
- * @param left The bottom of this view, in pixels.
+ * @param left The left of this view, in pixels.
*/
public final void setLeft(int left) {
if (left != mLeft) {
@@ -10608,7 +10608,7 @@
* by the layout system and should not generally be called otherwise, because the property
* may be changed at any time by the layout.
*
- * @param right The bottom of this view, in pixels.
+ * @param right The right of this view, in pixels.
*/
public final void setRight(int right) {
if (right != mRight) {
@@ -17235,7 +17235,7 @@
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
- setMeasuredDimension((int) (value >> 32), (int) value);
+ setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
@@ -17330,6 +17330,22 @@
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
+ setMeasuredDimensionRaw(measuredWidth, measuredHeight);
+ }
+
+ /**
+ * Sets the measured dimension without extra processing for things like optical bounds.
+ * Useful for reapplying consistent values that have already been cooked with adjustments
+ * for optical bounds, etc. such as those from the measurement cache.
+ *
+ * @param measuredWidth The measured width of this view. May be a complex
+ * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
+ * {@link #MEASURED_STATE_TOO_SMALL}.
+ * @param measuredHeight The measured height of this view. May be a complex
+ * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
+ * {@link #MEASURED_STATE_TOO_SMALL}.
+ */
+ private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
@@ -19142,7 +19158,18 @@
}
static int adjust(int measureSpec, int delta) {
- return makeMeasureSpec(getSize(measureSpec + delta), getMode(measureSpec));
+ final int mode = getMode(measureSpec);
+ if (mode == UNSPECIFIED) {
+ // No need to adjust size for UNSPECIFIED mode.
+ return makeMeasureSpec(0, UNSPECIFIED);
+ }
+ int size = getSize(measureSpec) + delta;
+ if (size < 0) {
+ Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
+ ") spec: " + toString(measureSpec) + " delta: " + delta);
+ size = 0;
+ }
+ return makeMeasureSpec(size, mode);
}
/**
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 941cdd0..438a9da 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -296,7 +296,7 @@
// The extra space for the thumb to move on the track
available += mThumbOffset * 2;
- int thumbPos = (int) (scale * available);
+ int thumbPos = (int) (scale * available + 0.5f);
int topBound, bottomBound;
if (gap == Integer.MIN_VALUE) {
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index bc051ce..03a053c 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -504,6 +504,7 @@
private String mEnabledInputMethodsStrCache;
private int mCurrentUserId;
+ private int[] mRelatedUserIds = new int[0];
private static void buildEnabledInputMethodsSettingString(
StringBuilder builder, Pair<String, ArrayList<String>> pair) {
@@ -536,6 +537,22 @@
mCurrentUserId = userId;
}
+ public void setRelatedUserIds(int[] relatedUserIds) {
+ synchronized (this) {
+ mRelatedUserIds = relatedUserIds;
+ }
+ }
+
+ public boolean isRelatedToOrCurrentUser(int userId) {
+ synchronized (this) {
+ if (userId == mCurrentUserId) return true;
+ for (int i = 0; i < mRelatedUserIds.length; i++) {
+ if (userId == mRelatedUserIds[i]) return true;
+ }
+ return false;
+ }
+ }
+
public List<InputMethodInfo> getEnabledInputMethodListLocked() {
return createEnabledInputMethodListLocked(
getEnabledInputMethodsAndSubtypeListLocked());
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index e50c1d8..94c3f44 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -818,13 +818,6 @@
mOptions.add(opt);
}
- /*
- * We don't have /tmp on the device, but we often have an SD card. Apps
- * shouldn't use this, but some test suites might want to exercise it.
- */
- opt.optionString = "-Djava.io.tmpdir=/sdcard";
- mOptions.add(opt);
-
initArgs.version = JNI_VERSION_1_4;
initArgs.options = mOptions.editArray();
initArgs.nOptions = mOptions.size();
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index fc587c0..2b9a5c4 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -115,7 +115,7 @@
}
}
- int32_t* yDivs = chunk->getXDivs();
+ int32_t* yDivs = chunk->getYDivs();
for (int i = 0; i < chunk->numYDivs; i++) {
yDivs[i] = int32_t(yDivs[i] * scale + 0.5f);
if (i > 0 && yDivs[i] == yDivs[i - 1]) {
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 7e958e7..a64d3ba 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -17,10 +17,12 @@
#define LOG_TAG "AudioTrack-JNI"
-#include <jni.h>
#include <JNIHelp.h>
+#include <JniConstants.h>
#include <android_runtime/AndroidRuntime.h>
+#include "ScopedBytes.h"
+
#include <utils/Log.h>
#include <media/AudioSystem.h>
#include <media/AudioTrack.h>
@@ -503,13 +505,13 @@
}
// ----------------------------------------------------------------------------
-jint writeToTrack(const sp<AudioTrack>& track, jint audioFormat, jbyte* data,
- jint offsetInBytes, jint sizeInBytes) {
+jint writeToTrack(const sp<AudioTrack>& track, jint audioFormat, const jbyte* data,
+ jint offsetInBytes, jint sizeInBytes, bool blocking = true) {
// give the data to the native AudioTrack object (the data starts at the offset)
ssize_t written = 0;
// regular write() or copy the data to the AudioTrack's shared memory?
if (track->sharedBuffer() == 0) {
- written = track->write(data + offsetInBytes, sizeInBytes);
+ written = track->write(data + offsetInBytes, sizeInBytes, blocking);
// for compatibility with earlier behavior of write(), return 0 in this case
if (written == (ssize_t) WOULD_BLOCK) {
written = 0;
@@ -563,7 +565,8 @@
static jint android_media_AudioTrack_write_byte(JNIEnv *env, jobject thiz,
jbyteArray javaAudioData,
jint offsetInBytes, jint sizeInBytes,
- jint javaAudioFormat) {
+ jint javaAudioFormat,
+ jboolean isWriteBlocking) {
//ALOGV("android_media_AudioTrack_write_byte(offset=%d, sizeInBytes=%d) called",
// offsetInBytes, sizeInBytes);
sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
@@ -590,7 +593,8 @@
return 0;
}
- jint written = writeToTrack(lpTrack, javaAudioFormat, cAudioData, offsetInBytes, sizeInBytes);
+ jint written = writeToTrack(lpTrack, javaAudioFormat, cAudioData, offsetInBytes, sizeInBytes,
+ isWriteBlocking == JNI_TRUE /* blocking */);
env->ReleaseByteArrayElements(javaAudioData, cAudioData, 0);
@@ -601,6 +605,31 @@
// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_write_native_bytes(JNIEnv *env, jobject thiz,
+ jbyteArray javaBytes, jint byteOffset, jint offsetInBytes, jint sizeInBytes,
+ jint javaAudioFormat, jboolean isWriteBlocking) {
+ //ALOGV("android_media_AudioTrack_write_native_bytes(offset=%d, sizeInBytes=%d) called",
+ // offsetInBytes, sizeInBytes);
+ sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+ if (lpTrack == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Unable to retrieve AudioTrack pointer for write()");
+ return 0;
+ }
+
+ ScopedBytesRO bytes(env, javaBytes);
+ if (bytes.get() == NULL) {
+ ALOGE("Error retrieving source of audio data to play, can't play");
+ return AUDIOTRACK_ERROR_BAD_VALUE;
+ }
+
+ jint written = writeToTrack(lpTrack, javaAudioFormat, bytes.get() + byteOffset, offsetInBytes,
+ sizeInBytes, isWriteBlocking == JNI_TRUE /* blocking */);
+
+ return written;
+}
+
+// ----------------------------------------------------------------------------
static jint android_media_AudioTrack_write_short(JNIEnv *env, jobject thiz,
jshortArray javaAudioData,
jint offsetInShorts, jint sizeInShorts,
@@ -608,7 +637,8 @@
jint written = android_media_AudioTrack_write_byte(env, thiz,
(jbyteArray) javaAudioData,
offsetInShorts*2, sizeInShorts*2,
- javaAudioFormat);
+ javaAudioFormat,
+ JNI_TRUE /*blocking write, legacy behavior*/);
if (written > 0) {
written /= 2;
}
@@ -890,7 +920,10 @@
(void *)android_media_AudioTrack_setup},
{"native_finalize", "()V", (void *)android_media_AudioTrack_finalize},
{"native_release", "()V", (void *)android_media_AudioTrack_release},
- {"native_write_byte", "([BIII)I", (void *)android_media_AudioTrack_write_byte},
+ {"native_write_byte", "([BIIIZ)I",(void *)android_media_AudioTrack_write_byte},
+ {"native_write_native_bytes",
+ "(Ljava/lang/Object;IIIIZ)I",
+ (void *)android_media_AudioTrack_write_native_bytes},
{"native_write_short", "([SIII)I", (void *)android_media_AudioTrack_write_short},
{"native_setVolume", "(FF)V", (void *)android_media_AudioTrack_set_volume},
{"native_get_native_frame_count",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4c0ddeb..b99cb90 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2069,6 +2069,14 @@
android:description="@string/permdesc_setPointerSpeed"
android:protectionLevel="signature" />
+ <!-- Allows low-level access to setting input device calibration.
+ <p>Not for use by normal applications.
+ @hide -->
+ <permission android:name="android.permission.SET_INPUT_CALIBRATION"
+ android:label="@string/permlab_setInputCalibration"
+ android:description="@string/permdesc_setInputCalibration"
+ android:protectionLevel="signature" />
+
<!-- Allows low-level access to setting the keyboard layout.
<p>Not for use by third-party applications.
@hide -->
diff --git a/core/res/res/drawable/btn_borderless_quantum_dark.xml b/core/res/res/drawable/btn_borderless_quantum_dark.xml
index e1bff4f..2c0d3b7 100644
--- a/core/res/res/drawable/btn_borderless_quantum_dark.xml
+++ b/core/res/res/drawable/btn_borderless_quantum_dark.xml
@@ -14,10 +14,6 @@
limitations under the License.
-->
-<reveal xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:drawable="@color/transparent" />
- <item>
- <nine-patch android:src="@drawable/btn_qntm_alpha"
- android:tint="@color/btn_default_pressed_quantum_dark" />
- </item>
-</reveal>
+<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tint="@color/btn_default_pressed_quantum_dark"
+ android:mask="@drawable/btn_qntm_alpha" />
diff --git a/core/res/res/drawable/btn_borderless_quantum_light.xml b/core/res/res/drawable/btn_borderless_quantum_light.xml
index e7a95b1..2faec20 100644
--- a/core/res/res/drawable/btn_borderless_quantum_light.xml
+++ b/core/res/res/drawable/btn_borderless_quantum_light.xml
@@ -14,10 +14,6 @@
limitations under the License.
-->
-<reveal xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:drawable="@color/transparent" />
- <item>
- <nine-patch android:src="@drawable/btn_qntm_alpha"
- android:tint="@color/btn_default_pressed_quantum_light" />
- </item>
-</reveal>
+<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tint="@color/btn_default_pressed_quantum_light"
+ android:mask="@drawable/btn_qntm_alpha" />
diff --git a/core/res/res/drawable/btn_color_quantum_dark.xml b/core/res/res/drawable/btn_color_quantum_dark.xml
index 5e44a78..0507755 100644
--- a/core/res/res/drawable/btn_color_quantum_dark.xml
+++ b/core/res/res/drawable/btn_color_quantum_dark.xml
@@ -14,21 +14,16 @@
limitations under the License.
-->
-<reveal xmlns:android="http://schemas.android.com/apk/res/android">
- <item>
- <selector>
- <item android:state_enabled="false">
- <nine-patch android:src="@drawable/btn_qntm_alpha"
- android:tint="@color/btn_default_normal_quantum_light" />
- </item>
- <item>
- <nine-patch android:src="@drawable/btn_qntm_alpha"
- android:tint="@color/theme_color_500" />
- </item>
- </selector>
- </item>
- <item>
- <nine-patch android:src="@drawable/btn_qntm_alpha"
- android:tint="@color/theme_color_300" />
- </item>
-</reveal>
+<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tint="@color/theme_color_300">
+ <selector>
+ <item android:state_enabled="false">
+ <nine-patch android:src="@drawable/btn_qntm_alpha"
+ android:tint="@color/btn_default_normal_quantum_dark" />
+ </item>
+ <item>
+ <nine-patch android:src="@drawable/btn_qntm_alpha"
+ android:tint="@color/theme_color_500" />
+ </item>
+ </selector>
+</touch-feedback>
diff --git a/core/res/res/drawable/btn_color_quantum_light.xml b/core/res/res/drawable/btn_color_quantum_light.xml
index d6be958..9166b8d 100644
--- a/core/res/res/drawable/btn_color_quantum_light.xml
+++ b/core/res/res/drawable/btn_color_quantum_light.xml
@@ -14,21 +14,16 @@
limitations under the License.
-->
-<reveal xmlns:android="http://schemas.android.com/apk/res/android">
- <item>
- <selector>
- <item android:state_enabled="false">
- <nine-patch android:src="@drawable/btn_qntm_alpha"
- android:tint="@color/btn_default_normal_quantum_dark" />
- </item>
- <item>
- <nine-patch android:src="@drawable/btn_qntm_alpha"
- android:tint="@color/theme_color_500" />
- </item>
- </selector>
- </item>
- <item>
- <nine-patch android:src="@drawable/btn_qntm_alpha"
- android:tint="@color/theme_color_700" />
- </item>
-</reveal>
+<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tint="@color/theme_color_700">
+ <selector>
+ <item android:state_enabled="false">
+ <nine-patch android:src="@drawable/btn_qntm_alpha"
+ android:tint="@color/btn_default_normal_quantum_dark" />
+ </item>
+ <item>
+ <nine-patch android:src="@drawable/btn_qntm_alpha"
+ android:tint="@color/theme_color_500" />
+ </item>
+ </selector>
+</touch-feedback>
diff --git a/core/res/res/drawable/btn_default_quantum_dark.xml b/core/res/res/drawable/btn_default_quantum_dark.xml
index 7f0cca8..29c3e24 100644
--- a/core/res/res/drawable/btn_default_quantum_dark.xml
+++ b/core/res/res/drawable/btn_default_quantum_dark.xml
@@ -14,13 +14,8 @@
limitations under the License.
-->
-<reveal xmlns:android="http://schemas.android.com/apk/res/android">
- <item>
- <nine-patch android:src="@drawable/btn_qntm_alpha"
- android:tint="@color/btn_default_normal_quantum_dark" />
- </item>
- <item>
- <nine-patch android:src="@drawable/btn_qntm_alpha"
- android:tint="@color/btn_default_pressed_quantum_dark" />
- </item>
-</reveal>
+<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tint="@color/btn_default_pressed_quantum_dark">
+ <nine-patch android:src="@drawable/btn_qntm_alpha"
+ android:tint="@color/btn_default_normal_quantum_dark" />
+</touch-feedback>
diff --git a/core/res/res/drawable/btn_default_quantum_light.xml b/core/res/res/drawable/btn_default_quantum_light.xml
index e391a80..c6e828c 100644
--- a/core/res/res/drawable/btn_default_quantum_light.xml
+++ b/core/res/res/drawable/btn_default_quantum_light.xml
@@ -14,13 +14,8 @@
limitations under the License.
-->
-<reveal xmlns:android="http://schemas.android.com/apk/res/android">
- <item>
- <nine-patch android:src="@drawable/btn_qntm_alpha"
- android:tint="@color/btn_default_normal_quantum_light" />
- </item>
- <item>
- <nine-patch android:src="@drawable/btn_qntm_alpha"
- android:tint="@color/btn_default_pressed_quantum_light" />
- </item>
-</reveal>
+<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tint="@color/btn_default_pressed_quantum_light">
+ <nine-patch android:src="@drawable/btn_qntm_alpha"
+ android:tint="@color/btn_default_normal_quantum_light" />
+</touch-feedback>
diff --git a/core/res/res/drawable/item_background_borderless_quantum_dark.xml b/core/res/res/drawable/item_background_borderless_quantum_dark.xml
index 1caee4e..3f32850 100644
--- a/core/res/res/drawable/item_background_borderless_quantum_dark.xml
+++ b/core/res/res/drawable/item_background_borderless_quantum_dark.xml
@@ -15,4 +15,4 @@
-->
<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="@color/lighter_gray" />
+ android:tint="@color/lighter_gray" />
diff --git a/core/res/res/drawable/item_background_borderless_quantum_light.xml b/core/res/res/drawable/item_background_borderless_quantum_light.xml
index ecf7dfb..09f6ae9 100644
--- a/core/res/res/drawable/item_background_borderless_quantum_light.xml
+++ b/core/res/res/drawable/item_background_borderless_quantum_light.xml
@@ -15,4 +15,4 @@
-->
<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="@color/darker_gray" />
+ android:tint="@color/darker_gray" />
diff --git a/core/res/res/drawable/item_background_quantum_dark.xml b/core/res/res/drawable/item_background_quantum_dark.xml
index 5ccaa8e..3f32850 100644
--- a/core/res/res/drawable/item_background_quantum_dark.xml
+++ b/core/res/res/drawable/item_background_quantum_dark.xml
@@ -14,7 +14,5 @@
limitations under the License.
-->
-<reveal xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:drawable="@color/transparent" />
- <item android:drawable="@color/lighter_gray" />
-</reveal>
+<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tint="@color/lighter_gray" />
diff --git a/core/res/res/drawable/item_background_quantum_light.xml b/core/res/res/drawable/item_background_quantum_light.xml
index f1453c5..09f6ae9 100644
--- a/core/res/res/drawable/item_background_quantum_light.xml
+++ b/core/res/res/drawable/item_background_quantum_light.xml
@@ -14,7 +14,5 @@
limitations under the License.
-->
-<reveal xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:drawable="@color/transparent" />
- <item android:drawable="@color/darker_gray" />
-</reveal>
+<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tint="@color/darker_gray" />
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 246bda1..0b9ac7f 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"deïnstalleer kortpaaie"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Laat die program toe om Tuisskerm-kortpaaie te verwyder sonder gebruikerinmenging."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"herlei uitgaande oproepe"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Laat die program toe om uitgaande oproepe te verwerk en die nommer wat geskakel moet word te verander. Hierdie toestemming laat die program toe om uitgaande oproepe te monitor, herlei, of te voorkom."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Laat die program toe om te sien watter nommer tydens \'n uitgaande oproep geskakel word, met die opsie om die oproep na \'n ander nommer te herlei of die oproep heeltemal te beëindig."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"ontvang teksboodskappe (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Laat die program toe om SMS-boodskappe te ontvang en te verwerk. Dit beteken dat die program boodskappe wat na jou toestel gestuur is, kan monitor of uitvee, sonder dat jy dit gesien het."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"ontvang teksboodskappe (MMS)"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index c92ecb3..5701852 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"አቋራጮችን ያራግፋል"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"መተግበሪያው ያለተጠቃሚ ጣልቃ-ገብነት የመነሻ ማያ ገጽ አቋራጮችን እንዲያስወግድ ያስችለዋል።"</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"የወጪ ጥሪዎች አቅጣጫ ቀይር"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"መተግበሪያው ወጪ ጥሪዎችን እንዲያስኬድና የሚደወለውን ቁጥር እንዲቀይር ይፈቅድለታል። ይህ ፈቃድ መተግበሪያው ወጪ ጥሪዎችን እንዲቆጣጠር፣ አቅጣጫ እንዲየስቀይር ወይም እንዲያግድ ይፈቅድለታል።"</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"በወጪ ጥሪ ጊዜ ጥሪውን ወደተለየ ቁጥር ከማዞር ወይም ጥሪውን በአጠቃላይ ከመተው አማራጭ ጋር እየተደወለለት ያለውን ቁጥር እንዲያይ ያስችለዋል።"</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"የፅሁፍ መልዕክቶችን ተቀበል (ኤስ.ኤም.ኤስ.)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"መተግበሪያው የኤስ.ኤም.ኤስ. መልእክቶችን እንዲያነብ እና እንዲያካሂድ ይፈቅዳል። ይህ ማለት መተግበሪያው ወደ መሳሪያህ የተላኩ መልእክቶችን ላንተ ሳያሳይህ ሊቆጣጠር ወይም ሊሰርዝ ይችላል።"</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"የፅሁፍ መልዕክቶችን ተቀበል (ኤም.ኤም.ኤስ.)"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 2c3549d..798ea7a 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"إزالة الاختصارات"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"للسماح للتطبيق بإزالة اختصارات من الشاشة الرئيسية بدون تدخل المستخدم."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"إعادة توجيه المكالمات الصادرة"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"للسماح للتطبيق بمعالجة المكالمات الصادرة وتغيير الرقم المطلوب. يتيح هذا الإذن للتطبيق مراقبة المكالمات الصادرة أو إعادة توجيهها أو منعها."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"للسماح للتطبيق بالاطلاع على الرقم الذي يتم الاتصال به عند إجراء مكالمة صادرة مع وجود الخيار لإعادة توجيه المكالمة إلى رقم آخر أو إنهاء المكالمة تمامًا."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"تلقي رسائل نصية (رسائل قصيرة SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"للسماح للتطبيق بتلقي ومعالجة الرسائل القصيرة SMS. وهذا يعني أنه يمكن للتطبيق مراقبة الرسائل التي يتم إرسالها إلى جهازك أو حذفها بدون عرضها لك."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"تلقي رسائل نصية (رسائل وسائط متعددة)"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index af7f9e8..f76ad7f 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"деинсталиране на преки пътища"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Разрешава на приложението да премахва преки пътища от началния екран без намеса на потребителя."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"пренасочване на изходящите обаждания"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Разрешава на приложението да обработва изходящите обаждания и да променя номера, който да се набере. Това разрешение му позволява да наблюдава, пренасочва или не допуска изходящи обаждания."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Разрешава на приложението да вижда набирания номер по време на изходящо обаждане и му дава възможност да пренасочи обаждането към друг номер или да го прекрати изцяло."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"получаване на текстови съобщения (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Разрешава на приложението да получава и обработва SMS съобщения. Това означава, че то може да наблюдава или изтрива изпратените до устройството ви, без да ви ги покаже."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"получаване на текстови съобщения (MMS)"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index cde9bdf..dc8128a 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"desinstal·la dreceres"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Permet que l\'aplicació suprimeixi les dreceres de la pantalla d\'inici sense la intervenció de l\'usuari."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"desviació de les trucades sortints"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Permet que l\'aplicació processi les trucades sortints i que canviï el número que es marcarà. Aquest permís permet que l\'aplicació supervisi, redirigeixi o bloquegi les trucades sortints."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Permet que l\'aplicació vegi el número que s\'està marcant durant una trucada sortint, amb l\'opció de redirigir la trucada a un altre número o bé de cancel·lar-la completament."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"recepció de missatges de text (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Permet que l\'aplicació rebi i processi missatges SMS. Això vol dir que l\'aplicació pot controlar o suprimir missatges que s\'han enviat al teu dispositiu sense mostrar-te\'ls."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"recepció de missatges de text (MMS)"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 010f812..9745cdc 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"odinstalace zástupců"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Umožňuje aplikaci odebrat zástupce z plochy bez zásahu uživatele."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"přesměrování odchozích hovorů"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Umožňuje aplikaci zpracovávat odchozí hovory a měnit vytáčené číslo. Toto oprávnění umožňuje sledovat či přesměrovat odchozí hovory nebo jim zabránit."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Umožňuje aplikaci sledovat při odchozích hovorech volaná čísla a přesměrovat hovor na jiné číslo nebo jej zcela zrušit."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"příjem textových zpráv (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Umožňuje aplikaci přijmout a zpracovat zprávy SMS. Znamená to, že aplikace může sledovat zprávy odeslané do vašeho zařízení nebo je smazat, aniž by se vám zobrazily."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"příjem textových zpráv (MMS)"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 07474b7..ca12213 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"afinstaller genveje"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Tillader, at applikationen fjerner genveje på startskærmen uden brugerindgriben."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"omdirigere udgående opkald"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Tillader, at appen kan behandle udgående opkald og ændre det nummer, der skal ringes til. Med denne tilladelse kan appen overvåge, omdirigere eller forhindre udgående opkald."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Tillader, at appen kan se det nummer, der ringes op til under et udgående opkald, og giver mulighed for at omdirigere opkaldet til et andet nummer eller afbryde opkaldet helt."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"modtage tekstbeskeder (sms)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Tillader, at appen kan modtage og behandle sms-beskeder. Det betyder, at appen kan overvåge eller slette de beskeder, der sendes til din enhed, uden at vise dem til dig."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"modtage tekstbeskeder (mms)"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 5d2515f..404d4cc 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"Verknüpfungen deinstallieren"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Ermöglicht einer App das Entfernen von Verknüpfungen vom Startbildschirm ohne Eingriff des Nutzers"</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"Ausgehende Anrufe umleiten"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Ermöglicht der App, ausgehende Anrufe zu verarbeiten und die zu wählende Nummer zu ändern. Die Berechtigung erlaubt der App, ausgehende Anrufe zu überwachen, umzuleiten und zu unterbinden."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Ermöglicht der App die Erkennung der während eines ausgehenden Anrufs gewählten Nummer und gibt ihr die Möglichkeit, den Anruf an eine andere Nummer umzuleiten oder den Anruf ganz abzubrechen"</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"SMS empfangen"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Ermöglicht der App, SMS zu empfangen und zu verarbeiten. Das bedeutet, dass die App an Ihr Gerät gesendete Nachrichten überwachen und löschen kann, ohne sie Ihnen anzuzeigen."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"MMS empfangen"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 8f297e1..1c3e185 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"κατάργηση εγκατάστασης συντομεύσεων"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Επιτρέπει στην εφαρμογή την κατάργηση συντομεύσεων από την Αρχική οθόνη χωρίς την παρέμβαση του χρήστη."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"αναδρομολόγηση εξερχόμενων κλήσεων"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Επιτρέπει στην εφαρμογή την επεξεργασία εξερχόμενων κλήσεων και την αλλαγή του αριθμού που πρόκειται να κληθεί. Αυτό δίνει τη δυνατότητα στην εφαρμογή να παρακολουθεί, να ανακατευθύνει ή να παρεμποδίζει εξερχόμενες κλήσεις:"</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Επιτρέπει στην εφαρμογή να βλέπει τον αριθμό που καλέσατε κατά τη διάρκεια μιας εξερχόμενης κλήσης με επιλογή ανακατεύθυνσης της κλήσης σε έναν διαφορετικό αριθμό ή διακοπής της κλήσης."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"λήψη μηνυμάτων κειμένου (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Επιτρέπει στην εφαρμογή τη λήψη και την επεξεργασία μηνυμάτων SMS. Αυτό σημαίνει ότι η εφαρμογή θα μπορούσε να παρακολουθήσει ή να διαγράψει τα μηνύματα που αποστέλλονται στη συσκευή σας χωρίς αυτά να εμφανιστούν σε εσάς."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"λήψη μηνυμάτων κειμένου (MMS)"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index c515849..c4b3a2b 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"uninstall shortcuts"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Allows the application to remove Home screen shortcuts without user intervention."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"reroute outgoing calls"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Allows the app to process outgoing calls and change the number to be dialled. This permission allows the app to monitor, redirect or prevent outgoing calls."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Allows the app to see the number being dialled during an outgoing call with the option to redirect the call to a different number or abort the call altogether."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"receive text messages (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Allows the app to receive and process SMS messages. This means that the app could monitor or delete messages sent to your device without showing them to you."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"receive text messages (MMS)"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index c515849..c4b3a2b 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"uninstall shortcuts"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Allows the application to remove Home screen shortcuts without user intervention."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"reroute outgoing calls"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Allows the app to process outgoing calls and change the number to be dialled. This permission allows the app to monitor, redirect or prevent outgoing calls."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Allows the app to see the number being dialled during an outgoing call with the option to redirect the call to a different number or abort the call altogether."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"receive text messages (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Allows the app to receive and process SMS messages. This means that the app could monitor or delete messages sent to your device without showing them to you."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"receive text messages (MMS)"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 545b317..1446aff 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"desinstalar accesos directos"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Permite que la aplicación elimine accesos directos de la pantalla principal sin que el usuario intervenga."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"redireccionar llamadas salientes"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Permite que la aplicación procese las llamadas salientes y cambie el número que se va a marcar. La aplicación puede utilizar este permiso para controlar o desviar llamadas salientes o para impedir que se realicen."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Permite que la aplicación vea el número marcado al realizar una llamada, con la opción de redirigir esta llamada a un número distinto o cancelarla completamente."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"recibir mensajes de texto (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Permite que la aplicación reciba y procese mensajes SMS, lo que significa que podría controlar o eliminar mensajes enviados al dispositivo sin mostrártelos."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"recibir mensajes de texto (MMS)"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 515caca..6d4ee8e 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"desinstalar accesos directos"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Permite que la aplicación elimine accesos directos de la pantalla de inicio sin la intervención del usuario."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"redireccionar llamadas salientes"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Permite que la aplicación procese las llamadas salientes y cambie el número que se va a marcar. La aplicación puede utilizar este permiso para controlar, desviar o impedir que se realicen llamadas salientes."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Permite que la aplicación vea el número que se marca al realizar una llamada con la opción de redirigir la llamada a otro número o cancelar la llamada."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"recibir mensajes de texto (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Permite que la aplicación reciba y procese mensajes MMS, lo que significa que podría utilizar este permiso para controlar o eliminar mensajes enviados al dispositivo sin mostrárselos al usuario."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"recibir mensajes de texto (MMS)"</string>
diff --git a/core/res/res/values-et-rEE/strings.xml b/core/res/res/values-et-rEE/strings.xml
index 988ab47..629852f 100644
--- a/core/res/res/values-et-rEE/strings.xml
+++ b/core/res/res/values-et-rEE/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"otseteede desinstallimine"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Lubab rakendusel eemaldada avaekraani otseteid ilma kasutaja sekkumiseta."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"marsruutige väljuvad kõned uuesti"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Võimaldab rakendusel töödelda väljuvaid kõnesid ja muuta valitavat numbrit. Luba võimaldab rakendusel jälgida, ümber suunata või takistada väljuvaid kõnesid."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Lubab rakendusel näha, mis number valitakse väljahelistamisel, ning laseb suunata kõne teisele numbrile või selle üldse katkestada."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"võtke vastu tekstisõnumeid (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Võimaldab rakendusel vastu võtta ja töödelda SMS-sõnumeid. See tähendab, et rakendus võib jälgida või kustutada teie seadmele saadetud sõnumeid neid teile näitamata."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"võtke vastu tekstisõnumeid (MMS)"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 1f2a6cc..a73d136 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"حذف نصب میانبرها"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"به برنامه اجازه میدهد میانبرهای صفحه اصلی را بدون دخالت کاربر حذف کند."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"ترسیم مجدد مسیر تماسهای خروجی"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"به برنامه اجازه میدهد تماسهای خروجی را پردازش کند و شمارههایی که باید گرفته شوند را تغییر دهد. این مجوز به برنامه امکان میدهد به کنترل، هدایت مجدد یا جلوگیری از تماسهای خروجی بپردازد."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"به برنامه اجازه میدهد عددی را که در طی یک تماس خروجی شمارهگیری شده ببیند و این اختیار را دارد که تماس را به شماره دیگری هدایت کند یا کلاً تماس را قطع کند."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"دریافت پیامهای نوشتاری (پیامک)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"به برنامه اجازه میدهد پیامکها را دریافت و پردازش کند. این یعنی برنامه میتواند پیامهای ارسالی به دستگاه شما را بدون نمایش آنها به شما حذف یا کنترل کند."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"دریافت پیامهای نوشتاری (MMS)"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index cbec12b..4e9ddd1 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"poista pikakuvakkeita"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Antaa sovelluksen poistaa aloitusruudun pikakuvakkeita ilman käyttäjän toimia."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"ohjaa uudelleen lähtevät puhelut"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Antaa sovelluksen käsitellä lähteviä puheluita ja muuttaa kohdenumeroita. Sovellus voi valvoa, uudelleenohjata tai estää lähteviä puheluita."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Sallii sovelluksen nähdä numeron, joka valitaan lähtevää puhelua soitettaessa, ja antaa mahdollisuuden ohjata puhelun eri numeroon tai keskeyttää puhelun kokonaan."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"vastaanota tekstiviestejä (teksti)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Antaa sovelluksen vastaanottaa ja käsitellä tekstiviestejä. Sovellus voi valvoa tai poistaa laitteeseesi lähetettyjä viestejä näyttämättä niitä sinulle."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"vastaanota tekstiviestejä (multimedia)"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 91749e6..79a1ab1 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"désinstaller des raccourcis"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Permet à l\'application de supprimer des raccourcis de la page d\'accueil sans intervention de l\'utilisateur."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"transférer les appels sortants"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Permet à l\'application de traiter les appels sortants et de modifier le numéro à composer. Cette autorisation lui permet de surveiller, rediriger ou empêcher les appels sortants."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Permet à l\'application de lire le numéro composé lors d\'un appel sortant et lui donne la possibilité de rediriger l\'appel vers un autre numéro ou d\'abandonner l\'appel."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"recevoir des messages texte"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Permet à l\'application de recevoir et de traiter les messages texte. Cette autorisation lui donne la possibilité de surveiller ou de supprimer les messages envoyés à votre appareil sans vous les montrer."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"recevoir des messages multimédias"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 51f0f76..f869af5 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"désinstaller des raccourcis"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Permettre à l\'application de supprimer des raccourcis de l\'écran d\'accueil sans l\'intervention de l\'utilisateur"</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"transférer les appels sortants"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Permet à l\'application de traiter les appels sortants et de modifier le numéro à composer. Cette autorisation lui permet de surveiller, rediriger ou empêcher les appels sortants."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Permettre à l\'application de lire le numéro composé lors d\'un appel sortant, et lui donner la possibilité de rediriger l\'appel vers un autre numéro ou d\'abandonner l\'appel"</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"recevoir des messages texte (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Permet à l\'application de recevoir et de traiter les SMS. Cette autorisation lui donne la possibilité de surveiller ou supprimer les messages envoyés à votre appareil sans vous les montrer."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"recevoir des messages texte (MMS)"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index b35c14f..00fdd7c 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"शॉर्टकट अनइंस्टॉल करें"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"एप्लिकेशन को उपयोगकर्ता के हस्तक्षेप के बिना होमस्क्रीन शॉर्टकट निकालने की अनुमति देता है."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"आउटगोइंग कॉल को कहीं और भेजें"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"ऐप्स को आउटगोइंग कॉल संसाधित करने और डायल किए जाने वाला नंबर बदलने देता है. यह अनुमति ऐप्स को आउटगोइंग कॉल की निगरानी करने, रीडायरेक्ट करने, या उन्हें रोकने देती है."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"ऐप्स को किसी कॉल को भिन्न नंबर पर रिडायरेक्ट करने या पूरी तरह से कॉल निरस्त करने के विकल्प के साथ आउटगोइंग कॉल के दौरान डायल किए जा रहे नंबर को देखने की अनुमति देती है."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"पाठ संदेश (SMS) प्राप्त करें"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"ऐप्स को SMS संदेशों को प्राप्त और संसाधित करने देता है. इसका अर्थ है कि ऐप्स आपके उपकरण पर भेजे गए संदेशों की निगरानी आपको दिखाए बिना कर सकता है और उन्हें हटा सकता है."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"पाठ संदेश (MMS) प्राप्त करें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index b048bbc..6224106 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"deinstaliranje prečaca"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Aplikaciji omogućuje uklanjanje prečaca početnog zaslona bez intervencije korisnika."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"preusmjeravanje odlaznih poziva"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Aplikaciji omogućuje obradu odlaznih poziva i promjenu broja za biranje. Ta dozvola aplikaciji omogućuje nadziranje, preusmjeravanje ili sprječavanje odlaznih poziva."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Omogućuje aplikaciji da vidi broj koji se bira prilikom odlaznog poziva uz opciju preusmjeravanja poziva na neki drugi broj ili prekidanja poziva."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"primanje tekstnih poruka (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Aplikaciji omogućuje primanje i obradu SMS poruka. To znači da aplikacija može nadzirati ili izbrisati poruke poslane na vaš uređaj, a da vam ih ne prikaže."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"primanje tekstnih poruka (MMS)"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 1547900..c0f9c27 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"parancsikonok eltávolítása"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Lehetővé teszi egy alkalmazás számára, hogy felhasználói beavatkozás nélkül távolítson el parancsikonokat a kezdőképernyőről."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"kimenő hívások átirányítása"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Lehetővé teszi az alkalmazás számára a kimenő hívások kezdeményezését, és a tárcsázandó szám módosítását. Az engedéllyel rendelkező alkalmazás felügyelheti, átirányíthatja vagy megakadályozhatja a kimenő hívásokat."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Lehetővé teszi, hogy az alkalmazás kimenő híváskor lássa a tárcsázott számot, valamint a hívást átirányítsa egy másik számra, vagy megszakítsa."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"szöveges üzenetek (SMS) fogadása"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Lehetővé teszi az alkalmazás számára, hogy SMS-eket fogadjon és dolgozzon fel. Ez azt jelenti, hogy az alkalmazás megfigyelheti vagy törölheti a beérkező üzeneteket anélkül, hogy Ön látná azokat."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"szöveges üzenetek (MMS) fogadása"</string>
diff --git a/core/res/res/values-hy-rAM/strings.xml b/core/res/res/values-hy-rAM/strings.xml
index 77b8070..8a0f901 100644
--- a/core/res/res/values-hy-rAM/strings.xml
+++ b/core/res/res/values-hy-rAM/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"ապատեղադրել դյուրանցումները"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Հավելվածին թույլ է տալիս հեռացնել գլխավոր էկրանի դյուրանցումները՝ առանց օգտագործողի միջամտության:"</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"վերաուղղել ելքային զանգերը"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Թույլ է տալիս հավելվածին մշակել ելքային զանգերը և փոխել համարհավաքումը: Վնասարար հավելվածները կարող են վերահսկել, վերահասցեավորել կամ կանխել ելքային զանգերը:"</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Թույլ է տալիս ծրագրին ելքային զանգի ընթացքում տեսնել արդեն հավաքած համարը՝ հնարավորություն տալով վերահղել կամ անջատել զանգը։"</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"ստանալ տեքստային հաղորդագրություններ (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Թույլ է տալիս հավելվածին ստանալ և մշակել SMS հաղորդագրությունները: Սա նշանակում է, որ հավելվածը կարող է ստուգել կամ ջնջել ձեր սարքին ուղարկված հաղորդագրությունները` առանց դրանք ձեզ ցուցադրելու:"</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"ստանալ տեքստային հաղորդագրություններ (MMS)"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index de49cba..07e6cb2 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"mencopot pemasangan pintasan"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Mengizinkan aplikasi menghapus pintasan Layar Utama tanpa tindakan dari pengguna."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"ubah rute panggilan keluar"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Memungkinkan aplikasi memproses panggilan keluar dan mengubah nomor yang akan dipanggil. Izin ini memungkinkan aplikasi memantau, mengalihkan, atau mencegah panggilan keluar."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Memungkinkan aplikasi melihat nomor yang dihubungi saat melakukan panggilan keluar dengan opsi untuk mengalihkan panggilan ke nomor lain atau membatalkan panggilan sepenuhnya."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"terima pesan teks (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Memungkinkan aplikasi menerima dan memproses pesan SMS. Ini artinya aplikasi dapat memantau atau menghapus pesan yang dikirim ke perangkat Anda tanpa menunjukkannya kepada Anda."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"terima pesan teks (MMS)"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 808a3be..0f59bba 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"eliminazione di scorciatoie"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Consente all\'applicazione di rimuovere le scorciatoie della schermata Home automaticamente."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"reindirizzamento chiamate in uscita"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Consente all\'applicazione di elaborare le chiamate in uscita e di modificare il numero da comporre. Questa autorizzazione consente all\'applicazione di monitorare, reindirizzare o impedire le chiamate in uscita."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Consente all\'app di rilevare il numero digitato durante una chiamata in uscita, con la possibilità di reindirizzare la telefonata a un numero diverso o interromperla del tutto."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"ricezione messaggi di testo (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Consente all\'applicazione di ricevere ed elaborare messaggi SMS. Significa che l\'applicazione potrebbe monitorare o eliminare i messaggi inviati al tuo dispositivo senza mostrarteli."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"ricezione messaggi di testo (MMS)"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index d3e1cdd..128fbc7 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"הסר התקנה של קיצורי דרך"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"מאפשר לאפליקציה להסיר קיצורי דרך במסך דף הבית ללא התערבות המשתמש."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"ניתוב מחדש של שיחות יוצאות"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"מאפשר לאפליקציה לעבד שיחות יוצאות ולשנות את המספר שיש לחייג. אישור זה מאפשר לאפליקציה לעקוב אחר שיחות יוצאות, לבצע הפניה מחדש שלהן או אף למנוע את ביצוען."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"מאפשרת לאפליקציה לראות את המספר המחויג במהלך ביצוע שיחה יוצאת, עם האפשרות להפנות את השיחה למספר אחר או לבטל את השיחה לחלוטין."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"קבלת הודעות טקסט (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"מאפשר לאפליקציה לקבל ולעבד הודעות SMS. משמעות הדבר היא שהאפליקציה יכולה לעקוב אחר הודעות שנשלחו למכשיר או למחוק אותן מבלי להציג לך אותן."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"קבלת הודעות טקסט (MMS)"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 8f5d7fe..7892d69 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"ショートカットのアンインストール"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"ユーザー操作なしでホーム画面のショートカットを削除することをアプリに許可します。"</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"発信先の変更"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"通話の発信とダイヤルする番号の変更をアプリに許可します。これにより、アプリが発信を監視、転送、阻止できるようになります。"</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"発信を別の番号に転送するか完全に中止するオプションで、発信中にダイヤルされた番号にアクセスすることをアプリに許可します。"</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"テキストメッセージ(SMS)の受信"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"SMSメッセージの受信と処理をアプリに許可します。これにより、アプリが端末に届いたメッセージを表示することなく監視または削除できるようになります。"</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"テキストメッセージ(MMS)の受信"</string>
diff --git a/core/res/res/values-ka-rGE/strings.xml b/core/res/res/values-ka-rGE/strings.xml
index 8bb038c..a48ac95 100644
--- a/core/res/res/values-ka-rGE/strings.xml
+++ b/core/res/res/values-ka-rGE/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"მალსახმობების წაშლა"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"მთავარ ეკრანზე აპლიკაციისთვის მალსახმობების დამოუკიდებლად წაშლის უფლების მიცემა."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"გამავალი ზარების გადამისამართება"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"აპს შეეძლება გამავალი ზარების დამუშავება და ასაკრეფი ნომრის შეცვლა. ეს უფლება აპს აძლევს შესაძლებლობას აკონტროლოს, გადაამისამართოს ან აღკვეთოს გამავალი ზარები."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"საშუალებას აძლევს აპს გამავალი ზარის დროს დაინახონ ზარის მიმღების ნომერი, ზარის სხვა მისამართზე გადამისამართებით ან ზარის საერთოდ შეწყვეტის საშუალებით."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"ტექსტური შეტყობინებების (SMS) მიღება"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"აპს შეეძლება SMS შეტყობინებების მიღება და დამუშავება. ეს ნიშნავს, რომ აპს შეეძლება თქვენ მოწყობილობაზე გამოგზავნილი შეტყობინებების მონიტორინგი და მათი წაშლა თქვენთვის ჩვენების გარეშე."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"ტექსტური შეტყობინებების (MMS) მიღება"</string>
diff --git a/core/res/res/values-km-rKH/strings.xml b/core/res/res/values-km-rKH/strings.xml
index 1d93e2e..488a439 100644
--- a/core/res/res/values-km-rKH/strings.xml
+++ b/core/res/res/values-km-rKH/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"លុបផ្លូវកាត់"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"ឲ្យកម្មវិធីលុបផ្លូវកាត់អេក្រង់ដើមដោយគ្មានអំពើពីអ្នកប្រើ។"</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"នាំផ្លូវការហៅចេញឡើងវិញ"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"ឲ្យកម្មវិធីដំណើរការការហៅចេញ និងប្ដូរលេខត្រូវហៅ។ សិទ្ធិនេះអនុញ្ញាតឲ្យកម្មវិធីតាមដាន ប្ដូរទិស ឬការពារការហៅចេញ។"</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"ឲ្យកម្មវិធីឃើញលេខដែលកំពុងត្រូវបានហៅអំឡុងពេលហៅចេញដោយប្រើជម្រើស ដើម្បីបញ្ជូនការហៅបន្តទៅលេខផ្សេង ឬបោះបង់ការហៅរួមគ្នា។"</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"ទទួលសារអត្ថបទ (សារ SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"ឲ្យកម្មវិធីទទួល និងដំណើរការសារ MMS ។ មានន័យថា កម្មវិធីអាចត្រួតពិនិត្យ ឬលុបសារដែលបានផ្ញើទៅឧបករណ៍របស់អ្នក ដោយមិនបង្ហាញអ្នក។"</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"ទទួលសារអត្ថបទ (MMS)"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 03542c5..681aabf 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"바로가기 제거"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"애플리케이션이 사용자의 작업 없이 홈 화면 바로가기를 삭제할 수 있도록 허용합니다."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"발신전화 경로 전환"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"앱이 발신 전화를 처리하고 전화를 걸 번호를 변경할 수 있도록 허용합니다. 이 권한을 사용하면 앱이 발신 전화를 모니터링, 리디렉션 또는 차단할 수도 있습니다."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"통화를 다른 번호로 리디렉션하거나 통화를 완전히 중단하는 옵션을 사용하여, 앱에서 발신 통화 중에 전화를 거는 번호를 볼 수 있게 허용합니다."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"문자 메시지 받기(SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"앱이 SMS 메시지를 수신하고 처리할 수 있도록 허용합니다. 이는 앱이 사용자에게 표시하지 않고 기기로 전송된 메시지를 모니터링 또는 삭제할 수도 있다는 것을 의미합니다."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"문자 메시지 받기(MMS)"</string>
diff --git a/core/res/res/values-lo-rLA/strings.xml b/core/res/res/values-lo-rLA/strings.xml
index 4c90dbe..64dcbc6 100644
--- a/core/res/res/values-lo-rLA/strings.xml
+++ b/core/res/res/values-lo-rLA/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"ຖອນທາງລັດ"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນລຶບທາງລັດໃນໜ້າຫຼັກໄດ້ ໂດຍບໍ່ຕ້ອງຮັບການຢືນຢັນຈາກຜູ່ໃຊ້."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"ປ່ຽນເສັ້ນທາງການໂທອອກ"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"ອະນຸຍາດໃຫ້ແອັບຯປະມວນຜົນສາຍທີ່ໂທອອກ ແລະປ່ຽນໝາຍເລກທີ່ຈະໂທອອກ. ແອັບຯທີ່ເປັນອັນຕະລາຍອາດກວດສອບ, ໂອນສາຍ ຫຼືຂັດຂວາງບໍ່ໃຫ້ໂທອອກໄດ້."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"ອະນຸຍາດໃຫ້ແອັບຯເບິ່ງໝາຍເລກເບີໂທ ໃນລະຫວ່າງການໂທອອກ ພ້ອມທັງໂຕເລືອກໃນການປ່ຽນເສັ້ນທາງການໂທໄປຫາເບີອື່ນ ຫຼື ລາຍລະອຽດກ່ຽວກັບເບີໂທລະສັບ."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"ຮັບຂໍ້ຄວາມສັ້ນ (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"ອະນຸຍາດໃຫ້ແອັບຯຮັບ ແລະປະມວນຜົນຂໍ້ຄວາມ SMS. ນີ້ໝາຍຄວາມວ່າແອັບຯສາມາດຕິດຕາມ ຫຼືລຶບຂໍ້ຄວາມທີ່ສົ່ງເຂົ້າອຸປະກອນຂອງທ່ານ ໂດຍທີ່ບໍ່ສະແດງພວກມັນໃຫ້ທ່ານເຫັນ."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"ຮັບຂໍ້ຄວາມ (MMS)"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index a8cb3d2..b379738 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"pašalinti sparčiuosius klavišus"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Programai leidžiama pašalinti sparčiuosius klavišus iš pagrindinio ekrano be naudotojo įsikišimo."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"peradresuoti išsiunčiamuosius skambučius"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Leidžiama programai atlikti išsiunčiamuosius skambučius ir keisti renkamą numerį. Šis leidimas suteikia teisę programai stebėti, peradresuoti ar neleisti išsiunčiamųjų skambučių."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Leidžiama programai peržiūrėti renkamą numerį išsiunčiamojo skambučio metu suteikiant galimybę peradresuoti skambutį kitu numeriu arba visiškai nutraukti skambutį."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"gauti teksto pranešimus (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Leidžiama programai gauti ir apdoroti SMS pranešimus. Tai reiškia, kad programa gali stebėti ir ištrinti į jūsų įrenginį siunčiamus pranešimus jums jų neparodžiusi."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"gauti teksto pranešimus (MMS)"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 33dd64f..278b0d7 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"atinstalēt saīsnes"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Ļauj lietojumprogrammai noņemt saīsnes no sākuma ekrāna, nejautājot lietotājam."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"pārmaršrutēt izejošos zvanus"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Ļauj lietotnei apstrādāt izejošos zvanus un mainīt numuru, uz kuru tiks zvanīts. Ar šo atļauju lietotne var pārraudzīt, novirzīt vai neatļaut izejošos zvanus."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Ļauj lietotnei skatīt ievadīto tālruņa numuru izejošā zvana laikā un piedāvā iespēju šo zvanu pāradresēt uz citu numuru vai vispār pārtraukt zvanu."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"saņemt īsziņas (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Ļauj lietotnei saņemt un apstrādāt īsziņas. Tas nozīmē, ka lietotne var pārraudzīt vai dzēst uz jūsu ierīci nosūtītos ziņojumus, neparādot tos jums."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"saņemt ziņojumus (MMS)"</string>
diff --git a/core/res/res/values-mn-rMN/strings.xml b/core/res/res/values-mn-rMN/strings.xml
index 1590063..3d0b3cb 100644
--- a/core/res/res/values-mn-rMN/strings.xml
+++ b/core/res/res/values-mn-rMN/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"товчлолыг устгах"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Аппликешн нь хэрэглэгчийн оролцоогүйгээр Нүүр дэлгэцний товчлолыг устгаж чадна."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"гарсан дуудлагыг чиглэлийг өөрчлөх"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Апп нь дуудлага хийх болон залгаж байгаа дугаарыг өөрчлөх боломжтой. Энэ зөвшөөрөл нь апп-г залгасан дуудлагыг хаах, хянах болон дахин чиглүүлэх боломжтой болгодог."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Гадагш дуудлага хийх үед залгасан дугаарыг харах, дуудлагыг өөр дугаар руу шилжүүлэх, таслах боломжтой болгоно."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"текст мессеж(SMS) хүлээж авах"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Апп нь SMS мессежийг хүлээн авах болон гүйцэтгэх боломжтой. Ингэснээр апп нь таны төхөөрөмжрүү илгээсэн мессежийг танд үзүүлэхгүйгээр хянах болон устгаж чадна."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"текст мессеж(МMS) хүлээж авах"</string>
@@ -963,7 +963,7 @@
<string name="permlab_setAlarm" msgid="1379294556362091814">"сэрүүлэг тохируулах"</string>
<string name="permdesc_setAlarm" msgid="316392039157473848">"Апп нь суулгагдсан сэрүүлэгний апп дээр сэрүүлэг тохируулах боломжтой. Зарим сэрүүлэгний апп нь энэ функцийг дэмжихгүй байж болзошгүй."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"дуут шуудан нэмэх"</string>
- <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Апп нь таны дуут шуудангийн ирсэн мэйлд мессеж нэмэх боломжтой."</string>
+ <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Таны дуут шуудангийн ирсэн мэйлд зурвас нэмэхийг апп-д зөвшөөрөх."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Хөтчийн геобайршлын зөвшөөрлийг өөрчлөх"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Апп нь Хөтчийн гео байршлын зөвшөөрлийг өөрчлөх боломжтой. Хортой апп нь энийг ашиглан дурын веб хуудасруу байршлын мэдээллийг илгээх боломжтой."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"багцийг тулгах"</string>
diff --git a/core/res/res/values-ms-rMY/strings.xml b/core/res/res/values-ms-rMY/strings.xml
index d5382ab..37d0d0f 100644
--- a/core/res/res/values-ms-rMY/strings.xml
+++ b/core/res/res/values-ms-rMY/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"nyahpasang pintasan"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Membenarkan aplikasi mengalih keluar pintasan Skrin Laman Utama tanpa campur tangan pengguna."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"tukar laluan panggilan keluar"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Membenarkan apl memproses panggilan keluar dan menukar nombor yang perlu didail. Kebenaran ini membolehkan apl memantau, mengalih atau menghalang panggilan keluar."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Membenarkan apl melihat nombor yang didail semasa panggilan keluar dengan pilihan untuk mengubah hala panggilan ke nombor lain atau membatalkan terus panggilan."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"terima mesej teks (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Membenarkan apl menerima dan memproses mesej SMS. Ini bermakna apl boleh memantau atau memadam mesej yang dihantar ke peranti anda tanpa menunjukkannya kepada anda."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"terima mesej teks (MMS)"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 6eae123..78df5e8 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"avinstallere snarveier"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Lar appen fjerne snarveier på startsiden uten å involvere brukeren."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"omdirigere utgående anrop"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Lar appen behandle utgående anrop og endre nummeret som skal ringes opp. Denne tillatelsen lar appen overvåke, viderekoble eller hindre utgående anrop."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Lar appen se nummeret det ringes til under en utgående samtale, med mulighet for å omdirigere anropet til et annet nummer eller avbryte samtalen fullstendig."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"motta tekstmeldinger (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Lar appen motta og behandle tekstmeldinger. Dette betyr at appen kan overvåke eller slette meldinger som er sendt til enheten din uten at du har sett dem."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"motta tekstmeldinger (MMS)"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 09497dc..8eaebf1 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"snelkoppelingen verwijderen"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"De app toestaan snelkoppelingen van het startscherm te verwijderen zonder tussenkomst van de gebruiker."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"uitgaande oproepen doorschakelen"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Hiermee kan de app uitgaande oproepen verwerken en het nummer wijzigen dat wordt gebeld. De app kan uitgaande oproepen bijhouden, omleiden of blokkeren."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"De app toestaan het nummer te bekijken dat wordt gekozen voor een uitgaande oproep, met de mogelijkheid de oproep om te leiden naar een ander nummer of de oproep helemaal af te breken."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"tekstberichten (SMS) ontvangen"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Hiermee kan de app sms-berichten ontvangen en verwerken. Dit betekent dat de app berichten die naar uw apparaat zijn verzonden, kan bijhouden of verwijderen zonder deze aan u weer te geven."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"tekstberichten (MMS) ontvangen"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 37dd42d..77d5441 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"odinstalowywanie skrótów"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Pozwala aplikacji usuwać skróty z ekranu głównego bez interwencji użytkownika."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"przekierowywanie połączeń wychodzących"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Pozwala aplikacji na przetwarzanie połączeń wychodzących i zmianę wybieranego numeru. Aplikacje z tym uprawnieniem mogą monitorować, przekierowywać lub blokować połączenia wychodzące."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Pozwala aplikacji na sprawdzenie numeru wybieranego w trakcie połączenia wychodzącego, a także umożliwia przerwanie połączenia lub przekierowanie go pod inny numer."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"odbieranie wiadomości tekstowych (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Pozwala aplikacji na odbieranie i przetwarzanie SMS-ów. To oznacza, że aplikacja będzie mogła bez Twojej wiedzy monitorować i usuwać wiadomości wysyłane do Twojego urządzenia."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"odbieranie wiadomości tekstowych (MMS)"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 6eb6cb2..e92961d 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"desinstalar atalhos"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Permite que a aplicação remova atalhos do Ecrã principal sem a intervenção do utilizador."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"redirecionar as chamadas efetuadas"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Permite que a aplicação processe chamadas efetuadas e mude o número a marcar. Esta autorização permite que a aplicação monitorize, redirecione ou impeça a realização de chamadas."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Permite que a aplicação veja o número que é marcado durante uma chamada efetuada, com a opção de redirecionar a chamada para um número diferente ou terminar a chamada."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"receber mensagens de texto (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Permite que a aplicação receba e processe mensagens SMS. Isto significa que a aplicação poderá monitorizar ou eliminar mensagens enviadas para o seu dispositivo sem as apresentar."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"receber mensagens de texto (MMS)"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 3a5b131..b3481b8 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"desinstalar atalhos"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Permite que o aplicativo remova atalhos da tela inicial sem a intervenção do usuário."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"redirecionar as chamadas efetuadas"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Permite que o aplicativo processe as chamadas de saída e altere o número a ser discado. Esta permissão autoriza o aplicativo a monitorar, redirecionar ou evitar chamadas de saída."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Permite que o aplicativo veja o número discado ao realizar uma chamada, com a opção de redirecionar a chamada para outro número ou abortá-la."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"receber mensagens de texto (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Permite que o aplicativo receba e processe mensagens SMS. Isso significa que o aplicativo pode monitorar ou excluir mensagens enviadas para o dispositivo sem mostrá-las para você."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"receber mensagens de texto (MMS)"</string>
diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml
index f252f50..9c2c793 100644
--- a/core/res/res/values-rm/strings.xml
+++ b/core/res/res/values-rm/strings.xml
@@ -363,7 +363,7 @@
<skip />
<!-- no translation found for permlab_processOutgoingCalls (3906007831192990946) -->
<skip />
- <!-- no translation found for permdesc_processOutgoingCalls (5331318931937402040) -->
+ <!-- no translation found for permdesc_processOutgoingCalls (5156385005547315876) -->
<skip />
<!-- no translation found for permlab_receiveSms (8673471768947895082) -->
<skip />
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 7610c06..016cb16 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"dezinstalează comenzi rapide"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Permite aplicației să elimine comenzi rapide de pe ecranul de pornire, fără intervenția utilizatorului."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"redirecţionează apelurile efectuate"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Permite aplicaţiei să proceseze apelurile efectuate şi să schimbe numărul care trebuie format. Cu această permisiune aplicaţia poate monitoriza, redirecţiona sau împiedica apelurile efectuate."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Permite aplicației să vadă numărul format în timpul unui apel de ieșire, cu opțiunea de a redirecționa apelul către un alt număr sau de a întrerupe apelul."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"primeşte mesaje text (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Permite aplicaţiei să primească şi să proceseze mesaje SMS. Acest lucru înseamnă că aplicaţia ar putea monitoriza sau şterge mesajele trimise pe dispozitivul dvs. fără a vi le arăta."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"primeşte mesaje text (MMS)"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 2ffc435..5c9e697 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"Удаление ярлыков"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Приложение сможет удалять ярлыки с главного экрана без вмешательства пользователя."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"Перенаправление исходящих вызовов"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Приложение сможет обрабатывать исходящие вызовы и изменять набираемые номера, а также отслеживать, перенаправлять или блокировать исходящие вызовы."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Приложение сможет видеть набранный номер во время исходящего вызова и при необходимости перенаправлять вызов или завершать его."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"Прием SMS-сообщений"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Приложение сможет получать и обрабатывать SMS. Это значит, что оно сможет отслеживать и удалять отправленные на ваше устройство сообщения, не показывая их."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"Прием MMS-сообщений"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index d3934a9..039e1eb 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"odinštalovať odkazy"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Povoľuje aplikácii odstrániť odkazy na ploche bez zásahu používateľa."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"presmerovať odchádzajúce hovory"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Umožňuje aplikácii spracovávať odchádzajúce hovory a meniť vytáčané číslo. Toto povolenie umožňuje aplikácii sledovať a presmerovať odchádzajúce hovory alebo im zabrániť."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Umožňuje aplikácii počas odchádzajúceho hovoru rozpoznať vytáčané číslo a poskytuje možnosť presmerovať daný hovor na odlišné číslo alebo ho úplne zrušiť."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"prijímať textové správy (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Umožňuje aplikácii prijímať a spracovávať správy SMS. Znamená to, že aplikácia môže sledovať správy odoslané na vaše zariadenie alebo ich odstrániť bez toho, aby sa vám zobrazili."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"prijímať textové správy (MMS)"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 783b0a4..6a909d6 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"odstranjevanje bližnjic"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Aplikaciji omogoča odstranjevanje bližnjic z začetnega zaslona brez posredovanja uporabnika."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"preusmeritev odhodnih klicev"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Aplikaciji omogoča, da obdela odhodne klice in spreminja klicne številke. S tem lahko aplikacija nadzira, preusmerja ali preprečuje odhodne klice."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Aplikaciji dovoli ogled klicane številke pri odhodnem klicu in ji omogoča preusmeritev klica na drugo številko ali prekinitev klica."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"prejemanje sporočil (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Aplikaciji omogoča prejemanje in obdelavo SMS-ov. S tem lahko aplikacija nadzoruje ali izbriše sporočila, poslana v napravo, ne da bi vam jih pokazala."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"prejemanje sporočil (MMS)"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index fb12e96..9a53a2a 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"деинсталирање пречица"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Омогућава апликацији да уклања пречице са почетног екрана без интервенције корисника."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"преусмеравање одлазних позива"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Дозвољава апликацији да обрађује одлазне позиве и промени број који се бира. Ова дозвола омогућава апликацији да надгледа, преусмерава или спречава одлазне позиве."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Дозвољава апликацији да види који број се бира при одлазном позиву уз опцију да преусмери позив на други број или га потпуно прекине."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"пријем текстуалних порука (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Дозвољава апликацији да прима и обрађује SMS поруке. То значи да апликација може да надгледа или брише поруке које се шаљу уређају, а да вам их не прикаже."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"пријем текстуалних порука (MMS)"</string>
@@ -1236,7 +1236,7 @@
<string name="sim_removed_message" msgid="2333164559970958645">"Мобилна мрежа неће бити доступна док не покренете систем поново уз уметање важеће SIM картице."</string>
<string name="sim_done_button" msgid="827949989369963775">"Готово"</string>
<string name="sim_added_title" msgid="3719670512889674693">"SIM картица је додата"</string>
- <string name="sim_added_message" msgid="6599945301141050216">"Поново покрените уређај да бисте могли да приступите мобилној мрежи."</string>
+ <string name="sim_added_message" msgid="6599945301141050216">"Рестартујте уређај да бисте могли да приступите мобилној мрежи."</string>
<string name="sim_restart_button" msgid="4722407842815232347">"Поново покрени"</string>
<string name="time_picker_dialog_title" msgid="8349362623068819295">"Подешавање времена"</string>
<string name="date_picker_dialog_title" msgid="5879450659453782278">"Подешавање датума"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index d6f7457..2e2d3f2 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"avinstallera genvägar"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Tillåter att appen tar bort genvägar på startskärmen utan åtgärd från användaren."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"omdirigera utgående samtal"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Tillåter att appen hanterar utgående samtal och ändrar numret som ska ringas upp. Med den här behörigheten kan appen övervaka, omdirigera eller förhindra utgående samtal."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Tillåter att appen ser numret du slår under ett utgående samtal och har möjlighet att koppla samtalet till ett annat nummer eller avbryta samtalet helt."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"ta emot textmeddelanden (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Tillåter att appen tar emot och hanterar SMS. Detta innebär att appen kan övervaka eller ta bort meddelanden som skickats till enheten utan att visa dem för dig."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"ta emot textmeddelanden (MMS)"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 7f17c76..5b15a7f 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"ondoa njia za mikato"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Huruhusu programu kuondoa njia za mkato za Skrini ya kwanza bila mtumiaji kuingilia."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"panga upya simu zinazotoka"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Inaruhusu programu kuchakata simu zinazotoka nje na kubadilisha nambari ya kupigwa. Idhini hii inaruhusu programu kuchunguza, kuelekeza upya, au kuzuia simu zinazotoka nje."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Huruhusu programu kuona nambari inayopigwa wakati simu inapigwa ikiwa na chaguo la kuelekeza simu kwa nambari tofauti au kukata simu kabisa."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"pokea ujumbe wa maandishi wa SMS"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Inaruhusu programu kupokea na kuchakata ujumbe wa SMS. Hii inamaanisha programu hii inaweza kuchunguza na kufuta ujumbe uliotumwa katika kifaa chako bila ya kukuonyesha."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"pokea ujumbe wa maandishi wa MMS"</string>
diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml
index d21f9b7..8f83ab2 100644
--- a/core/res/res/values-sw600dp/dimens.xml
+++ b/core/res/res/values-sw600dp/dimens.xml
@@ -22,6 +22,10 @@
<dimen name="thumbnail_width">200dp</dimen>
<!-- The height that is used when creating thumbnails of applications. -->
<dimen name="thumbnail_height">177dp</dimen>
+ <!-- The width that is used when creating thumbnails of applications. -->
+ <dimen name="recents_thumbnail_width">512dp</dimen>
+ <!-- The height that is used when creating thumbnails of applications. -->
+ <dimen name="recents_thumbnail_height">512dp</dimen>
<!-- The maximum number of action buttons that should be permitted within
an action bar/action mode. This will be used to determine how many
showAsAction="ifRoom" items can fit. "always" items can override this. -->
diff --git a/core/res/res/values-sw720dp/dimens.xml b/core/res/res/values-sw720dp/dimens.xml
index ccdb4be..040bb5b 100644
--- a/core/res/res/values-sw720dp/dimens.xml
+++ b/core/res/res/values-sw720dp/dimens.xml
@@ -38,6 +38,10 @@
<dimen name="thumbnail_width">230dp</dimen>
<!-- The height that is used when creating thumbnails of applications. -->
<dimen name="thumbnail_height">135dp</dimen>
+ <!-- The width that is used when creating thumbnails of applications. -->
+ <dimen name="recents_thumbnail_width">512dp</dimen>
+ <!-- The height that is used when creating thumbnails of applications. -->
+ <dimen name="recents_thumbnail_height">512dp</dimen>
<!-- Preference activity, vertical padding for the header list -->
<dimen name="preference_screen_header_vertical_padding">32dp</dimen>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index f59b2d1..a4c328e 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"ถอนการติดตั้งทางลัด"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"อนุญาตให้แอปพลิเคชันลบทางลัดหน้าจอหลักโดยไม่ต้องให้ผู้ใช้จัดการ"</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"จัดเส้นทางการโทรออกใหม่"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"อนุญาตให้แอปพลิเคชันประมวลผลการโทรออกและเปลี่ยนแปลงหมายเลขที่จะโทรไป การอนุญาตนี้จะทำให้แอปพลิเคชันสามารถตรวจสอบ เปลี่ยนเส้นทาง หรือกีดขวางไม่ให้โทรออกได้"</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"อนุญาตให้แอปดูหมายเลขที่โทรในระหว่างการโทรออกโดยสามารถเลือกเปลี่ยนเส้นทางการโทรไปยังหมายเลขอื่นหรือยกเลิกการโทรไปเลย"</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"รับข้อความ (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"อนุญาตให้แอปพลิเคชันรับและประมวลผลข้อความ SMS ซึ่งหมายความว่าแอปพลิเคชันจะสามารถตรวจสอบหรือลบข้อความที่ส่งมายังอุปกรณ์ของคุณได้โดยไม่ต้องแสดงให้คุณเห็น"</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"รับข้อความ (MMS)"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 0f5f53c..4c54352 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"i-uninstall ang mga shortcut"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Pinapayagan ang application na alisin ang mga shortcut ng Homescreen nang walang panghihimasok ng user."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"baguhin ang ruta ng mga papalabas na tawag"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Pinapayagan ang app na magproseso ng mga papalabas na tawag at baguhin ang numerong ida-dial. Pinapayagan ng pahintulot na ito ang app na sumubaybay, mag-redirect, o pumigil ng mga papalabas na tawag."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Pinapayagan ang app na makita ang numerong idina-dial sa isang papalabas na tawag na may opsyon na i-redirect ang tawag sa ibang numero o itigil ang tawag nang tuluyan."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"tumanggap ng mga text message (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Pinapayagan ang app na tumanggap at magproseso ng mga mensaheng SMS. Nangangahulugan ito na maaaring sumubaybay o magtanggal ang app ng mga mensaheng ipinapadala sa iyong device nang hindi ipinapakita ang mga ito sa iyo."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"tumanggap ng mga text message (MMS)"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 35b1241..bf01a4e 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"kısayolların yüklemesini kaldırma"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Uygulamaya, kullanıcının müdahalesi olmadan kısayolları Ana Ekrandan kaldırma izni verir."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"giden çağrıları yeniden yönlendir"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Uygulamaya, yapılan çağrıları işleme ve aranacak numarayı değiştirme izni verir. Bu izin, uygulamanın yapılan çağrıları izlemesine, yönlendirmesine ve önlemesine olanak sağlar."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Uygulamaya, giden bir çağrının numarası çevrilirken çağrıyı farklı bir numaraya yönlendirme ya da tamamen kapatma seçeneğiyle birlikte numarayı görme izni verir."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"kısa mesajları al (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Uygulamaya SMS mesajlarını alma ve işleme izni verir. Bu izin, uygulamanın cihazınıza gönderilen mesajları takip edip size göstermeden silebileceği anlamına gelir."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"kısa mesajları (MMS) al"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 3a5f184..dc79da7 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"видаляти ярлики"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Дозволяє програмі самостійно вилучати ярлики з головного екрана."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"переадресовувати вихідні виклики"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Дозволяє програмі обробляти вихідні дзвінки та змінювати номер для виклику. Такий дозвіл дає програмі змогу відстежувати, переадресовувати чи блокувати вихідні дзвінки."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Дозволяє додатку читати номер вихідного дзвінка, переспрямовувати дзвінок на інший номер або переривати його."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"отримувати текстові повідомлення (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Дозволяє програмі отримувати й обробляти SMS-повідомлення. Це означає, що програма може відстежувати чи видаляти повідомлення, надіслані на ваш пристрій, навіть не показуючи їх вам."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"отримувати текстові повідомлення (MMS)"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 4b5e48a..5dcf41d 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"gỡ cài đặt lối tắt"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Cho phép ứng dụng xóa lối tắt trên Màn hình chính mà không cần sự can thiệp của người dùng."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"định tuyến lại cuộc gọi đi"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Cho phép ứng dụng xử lý cuộc gọi đi và thay đổi số được gọi. Quyền này cho phép ứng dụng theo dõi, chuyển hướng hoặc chặn cuộc gọi đi."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Cho phép ứng dụng xem số được quay trong một cuộc gọi đi với tùy chọn chuyển hướng cuộc gọi đến một số khác hoặc hủy cuộc gọi đó hoàn toàn."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"nhận tin nhắn văn bản (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Cho phép ứng dụng nhận và xử lý tin nhắn SMS. Điều này có nghĩa là ứng dụng có thể theo dõi hoặc xóa tin nhắn được gửi đến thiết bị của bạn mà không hiển thị chúng cho bạn."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"nhận tin nhắn văn bản (MMS)"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index ebf11c4..5a9c285 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"卸载快捷方式"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"允许应用自行删除主屏幕快捷方式。"</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"重新设置外拨电话的路径"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"允许该应用处理外拨电话以及更改要拨打的号码。此权限可让该应用监视、重定向或阻止外拨电话。"</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"允许应用在拨出电话时查看拨打的电话号码,并选择改为拨打其他号码或完全中止通话。"</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"接收讯息(短信)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"允许该应用接收和处理短信。这就意味着,该应用可能会监视发送到您设备的短信,或删除发送到您设备的短信而不向您显示。"</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"接收讯息(彩信)"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 303affa..bfffe13 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"解除安裝捷徑"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"允許應用程式繞過用戶授權直接移除主畫面捷徑。"</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"重新設定撥出電話的路徑"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"允許應用程式處理撥出電話及更改撥打的號碼。這項權限允許應用程式監控、轉接或阻止撥出的電話。"</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"允許應用程式在撥出電話時查看所撥打的電話號碼,並選擇將電話重新導向至另一個號碼或完全中斷通話。"</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"接收短訊 (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"允許應用程式接收和處理短訊。這表示應用程式可監控傳送至您裝置的訊息,或在您閱讀訊息前擅自刪除訊息。"</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"接收短訊 (MMS)"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 544fd5b..d6bcb61 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"解除安裝捷徑"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"允許應用程式自動移除主螢幕捷徑。"</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"重設撥號路徑"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"允許應用程式處理撥出電話及更改撥打的號碼。這項權限可讓應用程式監控、轉接或阻止撥出的電話。"</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"允許應用程式在撥打電話期間查看撥出的電話號碼,並選擇改撥其他號碼或完全中斷通話。"</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"接收簡訊 (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"允許應用程式接收和處理簡訊。這項設定可讓應用程式監控傳送至您裝置的訊息,或在您閱讀訊息前擅自刪除訊息。"</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"接收簡訊 (MMS)"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 802fc0f..a44c04c 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -256,7 +256,7 @@
<string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"khipha izinqamuleli"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Ivumela uhlelo lokusebenza ukususa izinqamuleli zesikrini sasekhaya ngaphandle kokungenela komsebenzisi."</string>
<string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"thumela amakholi aphumayo kabusha"</string>
- <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Ivumela uhlelo lokusebenza ukucubungula amakholi aphumayo futhi ishintshe inombolo ezoshayelwa. Le mvume ivumela uhlelo lokusebenza ukwengamela, liqondise kabusha, noma livikele amakholi aphumayo."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Ivumela uhlelo lokusebenza ukubona inombolo eshayelwayo ngesikhathi sekholi ephumayo ngenketho yokuqondisa kabusha ikholi kwinombolo ehlukile noma ukuyekisa ikholi yonke."</string>
<string name="permlab_receiveSms" msgid="8673471768947895082">"thola imiyalezo ebhaliwe (i-SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Ivumela uhlelo lokusebenza ukuthola nokucubungula imilayezo ye-SMS. Loku kuchaza ukuthi uhlelo lokusebenza lungangamela noma lesuse imilayezo ethunyelwe kudivayisi yakho ngaphandle kokukubonisa yona."</string>
<string name="permlab_receiveMms" msgid="1821317344668257098">"thola imiyalezo ebhaliwe (i-MMS)"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index f85b193..2043960 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4429,6 +4429,20 @@
<attr name="color" />
</declare-styleable>
+ <!-- Drawable used to show animated touch feedback. -->
+ <declare-styleable name="TouchFeedbackDrawable">
+ <!-- The tint to use for feedback ripples. This attribute is mandatory. -->
+ <attr name="tint" />
+ <!-- Specifies the Porter-Duff blending mode used to apply the tint. The default vlaue is src_atop, which draws over the opaque parts of the drawable. -->
+ <attr name="tintMode" />
+ <!-- Whether to pin feedback ripples to the center of the drawable. Default value is false. -->
+ <attr name="pinned" format="boolean" />
+ <!-- Optional drawable used to mask ripple bounds before projection. -->
+ <attr name="mask" format="reference" />
+ <!-- Optional drawable onto which ripples are projected. -->
+ <attr name="drawable" />
+ </declare-styleable>
+
<declare-styleable name="ScaleDrawable">
<!-- Scale width, expressed as a percentage of the drawable's bound. The value's
format is XX%. For instance: 100%, 12.5%, etc.-->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 8c8c322..8ec2e6f 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -22,6 +22,10 @@
<dimen name="thumbnail_width">164dp</dimen>
<!-- The height that is used when creating thumbnails of applications. -->
<dimen name="thumbnail_height">145dp</dimen>
+ <!-- The width that is used when creating thumbnails of applications. -->
+ <dimen name="recents_thumbnail_width">256dp</dimen>
+ <!-- The height that is used when creating thumbnails of applications. -->
+ <dimen name="recents_thumbnail_height">256dp</dimen>
<!-- The standard size (both width and height) of an application icon that
will be displayed in the app launcher and elsewhere. -->
<dimen name="app_icon_size">48dip</dimen>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 3ab3e5d..d58e8ad 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2088,7 +2088,7 @@
<public type="style" name="Theme.DeviceDefault.Light.NoActionBar.TranslucentDecor" id="0x010301e4" />
<!-- ===============================================================
- Resources added in version 20 of the platform
+ Resources added in version 21 of the platform
=============================================================== -->
<eat-comment />
@@ -2106,6 +2106,10 @@
<public type="attr" name="transitionGroup" />
<public type="attr" name="castsShadow" />
<public type="attr" name="requiredForProfile"/>
+ <public type="attr" name="pinned" />
+
+ <public type="dimen" name="recents_thumbnail_height" />
+ <public type="dimen" name="recents_thumbnail_width" />
<public type="id" name="shared_element_name" />
@@ -2311,4 +2315,10 @@
<public type="style" name="Quantum.Light.ButtonBar.AlertDialog" />
<public type="style" name="Quantum.Light.ButtonBar" />
<public type="style" name="Quantum.Light.SegmentedButton" />
+
+ <public type="style" name="Widget.Quantum.Button.Paper" />
+ <public type="style" name="Widget.Quantum.Button.Paper.Color" />
+
+ <public type="style" name="Widget.Quantum.Light.Button.Paper" />
+ <public type="style" name="Widget.Quantum.Light.Button.Paper.Color" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index afb7085..f1bcf65 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2012,6 +2012,10 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_accessNetworkConditions">Allows an application to listen for observations on network conditions. Should never be needed for normal apps.</string>
+ <string name="permlab_setInputCalibration">change input device calibration</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_setInputCalibration">Allows the app to modify the calibration parameters of the touch screen. Should never be needed for normal apps.</string>
+
<!-- Policy administration -->
<!-- Title of policy access to limiting the user's password choices -->
diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java
new file mode 100644
index 0000000..e6a755f
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java
@@ -0,0 +1,317 @@
+/*
+ * 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.
+ */
+
+package android.graphics.drawable;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Rect;
+import android.graphics.Xfermode;
+
+/**
+ * A Drawable that wraps another Drawable.
+ */
+class DrawableWrapper extends Drawable implements Drawable.Callback {
+ private WrapperState mWrapperState;
+
+ /** Local drawable backed by its own constant state. */
+ private Drawable mWrappedDrawable;
+
+ private boolean mMutated;
+
+ /** @hide */
+ @Override
+ public boolean isProjected() {
+ return mWrappedDrawable.isProjected();
+ }
+
+ @Override
+ public void setAutoMirrored(boolean mirrored) {
+ mWrappedDrawable.setAutoMirrored(mirrored);
+ }
+
+ @Override
+ public boolean isAutoMirrored() {
+ return mWrappedDrawable.isAutoMirrored();
+ }
+
+ @Override
+ public int getMinimumWidth() {
+ return mWrappedDrawable.getMinimumWidth();
+ }
+
+ @Override
+ public int getMinimumHeight() {
+ return mWrappedDrawable.getMinimumHeight();
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mWrappedDrawable.getIntrinsicWidth();
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mWrappedDrawable.getIntrinsicHeight();
+ }
+
+ @Override
+ public Drawable getCurrent() {
+ return mWrappedDrawable.getCurrent();
+ }
+
+ @Override
+ public void invalidateDrawable(Drawable who) {
+ final Callback callback = getCallback();
+ if (callback != null) {
+ callback.invalidateDrawable(this);
+ }
+ }
+
+ @Override
+ public void scheduleDrawable(Drawable who, Runnable what, long when) {
+ final Callback callback = getCallback();
+ if (callback != null) {
+ callback.scheduleDrawable(this, what, when);
+ }
+ }
+
+ @Override
+ public void unscheduleDrawable(Drawable who, Runnable what) {
+ final Callback callback = getCallback();
+ if (callback != null) {
+ callback.unscheduleDrawable(this, what);
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ mWrappedDrawable.draw(canvas);
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mWrappedDrawable.getChangingConfigurations();
+ }
+
+ @Override
+ public boolean getPadding(Rect padding) {
+ return mWrappedDrawable.getPadding(padding);
+ }
+
+ @Override
+ public Rect getDirtyBounds() {
+ return mWrappedDrawable.getDirtyBounds();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean supportsHotspots() {
+ return mWrappedDrawable.supportsHotspots();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void setHotspot(int id, float x, float y) {
+ mWrappedDrawable.setHotspot(id, x, y);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void removeHotspot(int id) {
+ mWrappedDrawable.removeHotspot(id);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void clearHotspots() {
+ mWrappedDrawable.clearHotspots();
+ }
+
+ @Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ // Must call through to super().
+ super.setVisible(visible, restart);
+ return mWrappedDrawable.setVisible(visible, restart);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mWrappedDrawable.setAlpha(alpha);
+ }
+
+ @Override
+ public int getAlpha() {
+ return mWrappedDrawable.getAlpha();
+ }
+
+ /** {@hide} */
+ @Override
+ public void setLayoutDirection(int layoutDirection) {
+ mWrappedDrawable.setLayoutDirection(layoutDirection);
+ }
+
+ /** {@hide} */
+ @Override
+ public int getLayoutDirection() {
+ return mWrappedDrawable.getLayoutDirection();
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ mWrappedDrawable.setColorFilter(cf);
+ }
+
+ @Override
+ public ColorFilter getColorFilter() {
+ return mWrappedDrawable.getColorFilter();
+ }
+
+ @Override
+ public void setFilterBitmap(boolean filter) {
+ mWrappedDrawable.setFilterBitmap(filter);
+ }
+
+ @Override
+ public void setXfermode(Xfermode mode) {
+ mWrappedDrawable.setXfermode(mode);
+ }
+
+ @Override
+ public int getOpacity() {
+ return mWrappedDrawable.getOpacity();
+ }
+
+ @Override
+ public boolean isStateful() {
+ return mWrappedDrawable.isStateful();
+ }
+
+ @Override
+ public final boolean setState(int[] stateSet) {
+ return super.setState(stateSet);
+ }
+
+ @Override
+ public final int[] getState() {
+ return super.getState();
+ }
+
+ @Override
+ protected boolean onStateChange(int[] state) {
+ // Don't override setState(), getState().
+ return mWrappedDrawable.setState(state);
+ }
+
+ @Override
+ protected boolean onLevelChange(int level) {
+ // Don't override setLevel(), getLevel().
+ return mWrappedDrawable.setLevel(level);
+ }
+
+ @Override
+ public final void setBounds(int left, int top, int right, int bottom) {
+ super.setBounds(left, top, right, bottom);
+ }
+
+ @Override
+ public final void setBounds(Rect bounds) {
+ super.setBounds(bounds);
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ // Don't override setBounds(), getBounds().
+ mWrappedDrawable.setBounds(bounds);
+ }
+
+ protected void setConstantState(WrapperState wrapperState, Resources res) {
+ mWrapperState = wrapperState;
+
+ // Load a new drawable from the constant state.
+ if (wrapperState == null || wrapperState.mWrappedConstantState == null) {
+ mWrappedDrawable = null;
+ } else if (res != null) {
+ mWrappedDrawable = wrapperState.mWrappedConstantState.newDrawable(res);
+ } else {
+ mWrappedDrawable = wrapperState.mWrappedConstantState.newDrawable();
+ }
+ }
+
+ @Override
+ public ConstantState getConstantState() {
+ return mWrapperState;
+ }
+
+ @Override
+ public Drawable mutate() {
+ if (!mMutated) {
+ mWrappedDrawable = mWrappedDrawable.mutate();
+ mMutated = true;
+ }
+ return this;
+ }
+
+ /**
+ * Sets the wrapped drawable and update the constant state.
+ *
+ * @param drawable
+ * @param res
+ */
+ protected final void setDrawable(Drawable drawable, Resources res) {
+ if (mWrappedDrawable != null) {
+ mWrappedDrawable.setCallback(null);
+ }
+
+ mWrappedDrawable = drawable;
+
+ if (drawable != null) {
+ drawable.setCallback(this);
+
+ mWrapperState.mWrappedConstantState = drawable.getConstantState();
+ } else {
+ mWrapperState.mWrappedConstantState = null;
+ }
+ }
+
+ protected final Drawable getDrawable() {
+ return mWrappedDrawable;
+ }
+
+ static abstract class WrapperState extends ConstantState {
+ ConstantState mWrappedConstantState;
+
+ WrapperState(WrapperState orig) {
+ if (orig != null) {
+ mWrappedConstantState = orig.mWrappedConstantState;
+ }
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mWrappedConstantState.getChangingConfigurations();
+ }
+ }
+}
diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
index 6fbcb53..f4f545c 100644
--- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
+++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
@@ -21,15 +21,18 @@
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.SparseArray;
+import com.android.internal.R;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -37,23 +40,9 @@
import java.util.ArrayList;
/**
- * An extension of LayerDrawable that is intended to react to touch hotspots
- * and reveal the second layer atop the first.
- * <p>
- * It can be defined in an XML file with the <code><reveal></code> element.
- * Each Drawable in the transition is defined in a nested <code><item></code>.
- * For more information, see the guide to <a href="{@docRoot}
- * guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
- *
- * @attr ref android.R.styleable#LayerDrawableItem_left
- * @attr ref android.R.styleable#LayerDrawableItem_top
- * @attr ref android.R.styleable#LayerDrawableItem_right
- * @attr ref android.R.styleable#LayerDrawableItem_bottom
- * @attr ref android.R.styleable#LayerDrawableItem_drawable
- * @attr ref android.R.styleable#LayerDrawableItem_id
* @hide
*/
-public class TouchFeedbackDrawable extends Drawable {
+public class TouchFeedbackDrawable extends DrawableWrapper {
private final Rect mTempRect = new Rect();
private final Rect mPaddingRect = new Rect();
@@ -77,32 +66,43 @@
/** Paint used to control appearance of ripples. */
private Paint mRipplePaint;
+ /** Paint used to control reveal layer masking. */
+ private Paint mMaskingPaint;
+
/** Target density of the display into which ripples are drawn. */
private float mDensity = 1.0f;
/** Whether the animation runnable has been posted. */
private boolean mAnimating;
- TouchFeedbackDrawable() {
- this(new TouchFeedbackState(null), null);
+ /** The drawable to use as the mask. */
+ private Drawable mMask;
+
+ /* package */TouchFeedbackDrawable() {
+ this(null, null);
}
TouchFeedbackDrawable(TouchFeedbackState state, Resources res) {
+ mState = new TouchFeedbackState(state);
+
+ setConstantState(mState, res);
+
if (res != null) {
mDensity = res.getDisplayMetrics().density;
}
-
- mState = state;
}
+
+ private void setConstantState(TouchFeedbackState wrapperState, Resources res) {
+ super.setConstantState(wrapperState, res);
- @Override
- public void setColorFilter(ColorFilter cf) {
- // Not supported.
- }
-
- @Override
- public void setAlpha(int alpha) {
- // Not supported.
+ // Load a new mask drawable from the constant state.
+ if (wrapperState == null || wrapperState.mMaskState == null) {
+ mMask = null;
+ } else if (res != null) {
+ mMask = wrapperState.mMaskState.newDrawable(res);
+ } else {
+ mMask = wrapperState.mMaskState.newDrawable();
+ }
}
@Override
@@ -112,9 +112,20 @@
}
@Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+
+ if (mMask != null) {
+ mMask.setBounds(bounds);
+ }
+ }
+
+ @Override
protected boolean onStateChange(int[] stateSet) {
- final ColorStateList stateList = mState.mColorStateList;
- if (stateList != null && mRipplePaint != null) {
+ super.onStateChange(stateSet);
+
+ if (mRipplePaint != null) {
+ final ColorStateList stateList = mState.mTint;
final int newColor = stateList.getColorForState(stateSet, 0);
final int oldColor = mRipplePaint.getColor();
if (oldColor != newColor) {
@@ -132,12 +143,12 @@
*/
@Override
public boolean isProjected() {
- return true;
+ return mState.mProjected;
}
@Override
public boolean isStateful() {
- return mState.mColorStateList != null && mState.mColorStateList.isStateful();
+ return super.isStateful() || mState.mTint.isStateful();
}
@Override
@@ -145,16 +156,78 @@
throws XmlPullParserException, IOException {
super.inflate(r, parser, attrs);
- final TypedArray a = r.obtainAttributes(
- attrs, com.android.internal.R.styleable.ColorDrawable);
- mState.mColorStateList = a.getColorStateList(
- com.android.internal.R.styleable.ColorDrawable_color);
+ final TypedArray a = r.obtainAttributes(attrs, R.styleable.TouchFeedbackDrawable);
+
+ mState.mTint = a.getColorStateList(R.styleable.TouchFeedbackDrawable_tint);
+ mState.mTintMode = Drawable.parseTintMode(
+ a.getInt(R.styleable.TouchFeedbackDrawable_tintMode, -1), Mode.SRC_ATOP);
+ mState.mPinned = a.getBoolean(R.styleable.TouchFeedbackDrawable_pinned, false);
+
+ if (mState.mTint == null) {
+ throw new XmlPullParserException(parser.getPositionDescription()
+ + ": <touch-feedback> tag requires a 'tint' attribute");
+ }
+
+ Drawable mask = a.getDrawable(R.styleable.TouchFeedbackDrawable_mask);
+ final int drawableRes = a.getResourceId(R.styleable.TouchFeedbackDrawable_drawable, 0);
a.recycle();
+ final Drawable dr;
+ if (drawableRes != 0) {
+ dr = r.getDrawable(drawableRes);
+ } else {
+ int type;
+ while ((type = parser.next()) == XmlPullParser.TEXT) {
+ // Find the next non-text element.
+ }
+
+ if (type == XmlPullParser.START_TAG) {
+ dr = Drawable.createFromXmlInner(r, parser, attrs);
+ } else {
+ dr = null;
+ }
+ }
+
+ // If no mask is set, implicitly use the lower drawable.
+ if (mask == null) {
+ mask = dr;
+ }
+
+ // If neither a mask not a bottom layer was specified, assume we're
+ // projecting onto a parent surface.
+ mState.mProjected = mask == null && dr == null;
+
+ if (dr != null) {
+ setDrawable(dr, r);
+ } else {
+ // For now at least, we MUST have a wrapped drawable.
+ setDrawable(new ColorDrawable(Color.TRANSPARENT), r);
+ }
+
+ setMaskDrawable(mask, r);
setTargetDensity(r.getDisplayMetrics());
}
/**
+ * Sets the wrapped drawable and update the constant state.
+ *
+ * @param drawable
+ * @param res
+ */
+ void setMaskDrawable(Drawable drawable, Resources res) {
+ mMask = drawable;
+
+ if (drawable != null) {
+ // Nobody cares if the mask has a callback.
+ drawable.setCallback(null);
+
+ mState.mMaskState = drawable.getConstantState();
+ } else {
+ mState.mMaskState = null;
+ }
+ }
+
+ /**
* Set the density at which this drawable will be rendered.
*
* @param metrics The display metrics for this drawable.
@@ -175,6 +248,9 @@
}
/**
+ * TODO: Maybe we should set hotspots for state/id combinations? So touch
+ * would be state_pressed and the pointer ID.
+ *
* @hide until hotspot APIs are finalized
*/
@Override
@@ -186,19 +262,22 @@
final Ripple ripple = mTouchedRipples.get(id);
if (ripple == null) {
+ final Rect bounds = getBounds();
final Rect padding = mPaddingRect;
getPadding(padding);
- final Rect bounds = getBounds();
- final Ripple newRipple = new Ripple(bounds, padding, bounds.exactCenterX(),
- bounds.exactCenterY(), mDensity);
+ if (mState.mPinned) {
+ x = bounds.exactCenterX();
+ y = bounds.exactCenterY();
+ }
+
+ final Ripple newRipple = new Ripple(bounds, padding, x, y, mDensity);
newRipple.enter();
mActiveRipples.add(newRipple);
mTouchedRipples.put(id, newRipple);
- } else {
- // TODO: How do we want to respond to movement?
- //ripple.move(x, y);
+ } else if (!mState.mPinned) {
+ ripple.move(x, y);
}
scheduleAnimation();
@@ -254,7 +333,7 @@
if (mAnimationRunnable == null) {
mAnimationRunnable = new Runnable() {
- @Override
+ @Override
public void run() {
mAnimating = false;
scheduleAnimation();
@@ -269,47 +348,77 @@
@Override
public void draw(Canvas canvas) {
+ // The lower layer always draws normally.
+ super.draw(canvas);
+
+ if (mActiveRipples == null || mActiveRipples.size() == 0) {
+ // No ripples to draw.
+ return;
+ }
+
final ArrayList<Ripple> activeRipples = mActiveRipples;
- if (activeRipples == null || activeRipples.isEmpty()) {
- // Nothing to draw, we're done here.
- return;
- }
+ final Drawable mask = mMask;
+ final Rect bounds = mask == null ? null : mask.getBounds();
- final ColorStateList stateList = mState.mColorStateList;
- if (stateList == null) {
- // No color, we're done here.
- return;
- }
-
- final int color = stateList.getColorForState(getState(), Color.TRANSPARENT);
- if (color == Color.TRANSPARENT) {
- // No color, we're done here.
- return;
- }
-
- if (mRipplePaint == null) {
- mRipplePaint = new Paint();
- mRipplePaint.setAntiAlias(true);
- }
-
- mRipplePaint.setColor(color);
-
- final int restoreCount = canvas.save();
-
- // Draw ripples directly onto the canvas.
+ // Draw ripples into a layer that merges using SRC_IN.
+ boolean hasRipples = false;
+ int rippleRestoreCount = -1;
int n = activeRipples.size();
for (int i = 0; i < n; i++) {
final Ripple ripple = activeRipples.get(i);
if (!ripple.active()) {
+ // TODO: Mark and sweep is more efficient.
activeRipples.remove(i);
i--;
n--;
} else {
- ripple.draw(canvas, mRipplePaint);
+ // If we're masking the ripple layer, make sure we have a layer first.
+ if (mask != null && rippleRestoreCount < 0) {
+ rippleRestoreCount = canvas.saveLayer(bounds.left, bounds.top,
+ bounds.right, bounds.bottom, getMaskingPaint(SRC_ATOP), 0);
+ canvas.clipRect(bounds);
+ }
+
+ hasRipples |= ripple.draw(canvas, getRipplePaint());
}
}
- canvas.restoreToCount(restoreCount);
+ // If we have ripples, mask them.
+ if (mask != null && hasRipples) {
+ canvas.saveLayer(bounds.left, bounds.top, bounds.right,
+ bounds.bottom, getMaskingPaint(DST_IN), 0);
+ mask.draw(canvas);
+ }
+
+ // Composite the layers if needed:
+ // 1. Mask DST_IN
+ // 2. Ripples SRC_ATOP
+ // 3. Lower n/a
+ if (rippleRestoreCount > 0) {
+ canvas.restoreToCount(rippleRestoreCount);
+ }
+ }
+
+ private Paint getRipplePaint() {
+ if (mRipplePaint == null) {
+ mRipplePaint = new Paint();
+ mRipplePaint.setAntiAlias(true);
+
+ final int color = mState.mTint.getColorForState(getState(), Color.TRANSPARENT);
+ mRipplePaint.setColor(color);
+ }
+ return mRipplePaint;
+ }
+
+ private static final PorterDuffXfermode SRC_ATOP = new PorterDuffXfermode(Mode.SRC_ATOP);
+ private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN);
+
+ private Paint getMaskingPaint(PorterDuffXfermode mode) {
+ if (mMaskingPaint == null) {
+ mMaskingPaint = new Paint();
+ }
+ mMaskingPaint.setXfermode(mode);
+ return mMaskingPaint;
}
@Override
@@ -322,39 +431,46 @@
final Rect rippleBounds = mTempRect;
final ArrayList<Ripple> activeRipples = mActiveRipples;
if (activeRipples != null) {
- final int N = activeRipples.size();
- for (int i = 0; i < N; i++) {
- activeRipples.get(i).getBounds(rippleBounds);
- drawingBounds.union(rippleBounds);
- }
+ final int N = activeRipples.size();
+ for (int i = 0; i < N; i++) {
+ activeRipples.get(i).getBounds(rippleBounds);
+ drawingBounds.union(rippleBounds);
+ }
}
dirtyBounds.union(drawingBounds);
+ dirtyBounds.union(super.getDirtyBounds());
return dirtyBounds;
}
@Override
public ConstantState getConstantState() {
+ // TODO: Can we just rely on super.getConstantState()?
return mState;
}
- static class TouchFeedbackState extends ConstantState {
- ColorStateList mColorStateList;
+ static class TouchFeedbackState extends WrapperState {
+ ConstantState mMaskState;
+ ColorStateList mTint;
+ Mode mTintMode;
+ boolean mPinned;
+ boolean mProjected;
public TouchFeedbackState(TouchFeedbackState orig) {
+ super(orig);
+
if (orig != null) {
- mColorStateList = orig.mColorStateList;
+ mTint = orig.mTint;
+ mTintMode = orig.mTintMode;
+ mMaskState = orig.mMaskState;
+ mPinned = orig.mPinned;
+ mProjected = orig.mProjected;
}
}
@Override
- public int getChangingConfigurations() {
- return 0;
- }
-
- @Override
public Drawable newDrawable() {
- return newDrawable(null);
+ return new TouchFeedbackDrawable(this, null);
}
@Override
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 3759108..5611efb 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -16,8 +16,13 @@
package android.media;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.nio.ByteBuffer;
+import java.nio.NioUtils;
+import android.annotation.IntDef;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -145,6 +150,28 @@
private final static String TAG = "android.media.AudioTrack";
+ /** @hide */
+ @IntDef({
+ WRITE_BLOCKING,
+ WRITE_NON_BLOCKING
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WriteMode {}
+
+ /**
+ * @hide CANDIDATE FOR PUBLIC API
+ * The write mode indicating the write operation will block until all data has been written,
+ * to be used in {@link #write(ByteBuffer, int, int, int)}.
+ */
+ public final static int WRITE_BLOCKING = 0;
+ /**
+ * @hide CANDIDATE FOR PUBLIC API
+ * The write mode indicating the write operation will return immediately after
+ * queuing as much audio data for playback as possible without blocking, to be used in
+ * {@link #write(ByteBuffer, int, int, int)}.
+ */
+ public final static int WRITE_NON_BLOCKING = 1;
+
//--------------------------------------------------------------------------
// Member variables
//--------------------
@@ -1084,7 +1111,8 @@
return ERROR_BAD_VALUE;
}
- int ret = native_write_byte(audioData, offsetInBytes, sizeInBytes, mAudioFormat);
+ int ret = native_write_byte(audioData, offsetInBytes, sizeInBytes, mAudioFormat,
+ true /*isBlocking*/);
if ((mDataLoadMode == MODE_STATIC)
&& (mState == STATE_NO_STATIC_DATA)
@@ -1141,6 +1169,75 @@
/**
+ * @hide CANDIDATE FOR PUBLIC API
+ * Writes the audio data to the audio sink for playback (streaming mode),
+ * or copies audio data for later playback (static buffer mode).
+ * In static buffer mode, copies the data to the buffer starting at its 0 offset, and the write
+ * mode is ignored.
+ * In streaming mode, the blocking behavior will depend on the write mode.
+ * @param audioData the buffer that holds the data to play, starting at the position reported
+ * by <code>audioData.position()</code>.
+ * <BR>Note that this method will not update the position in this buffer, therefore when
+ * writing a loop to write all the data in the buffer, you should increment the
+ * <code>offsetInBytes</code> parameter at each pass by the amount that was previously
+ * written for this buffer.
+ * @param offsetInBytes offset to read from in bytes (note this differs from
+ * <code>audioData.position()</code>).
+ * @param sizeInBytes number of bytes to read (note this differs from
+ * <code>audioData.remaining()</code>).
+ * @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}. It has no
+ * effect in static mode.
+ * <BR>With {@link #WRITE_BLOCKING}, the write will block until all data has been written
+ * to the audio sink.
+ * <BR>With {@link #WRITE_NON_BLOCKING}, the write will return immediately after
+ * queuing as much audio data for playback as possible without blocking.
+ * @return 0 or a positive number of bytes that were written, or
+ * {@link #ERROR_BAD_VALUE}, {@link #ERROR_INVALID_OPERATION}
+ */
+ public int write(ByteBuffer audioData, int offsetInBytes, int sizeInBytes,
+ @WriteMode int writeMode) {
+
+ if (mState == STATE_UNINITIALIZED) {
+ Log.e(TAG, "AudioTrack.write() called in invalid state STATE_UNINITIALIZED");
+ return ERROR_INVALID_OPERATION;
+ }
+
+ if ((writeMode != WRITE_BLOCKING) && (writeMode != WRITE_NON_BLOCKING)) {
+ Log.e(TAG, "AudioTrack.write() called with invalid blocking mode");
+ return ERROR_BAD_VALUE;
+ }
+
+ if ( (audioData == null) || (offsetInBytes < 0 ) || (sizeInBytes < 0)
+ || (offsetInBytes + sizeInBytes < 0) // detect integer overflow
+ || (offsetInBytes + sizeInBytes > audioData.remaining())) {
+ Log.e(TAG, "AudioTrack.write() called with invalid size/offset values");
+ return ERROR_BAD_VALUE;
+ }
+
+ int ret = 0;
+ if (audioData.isDirect()) {
+ ret = native_write_native_bytes(audioData,
+ audioData.position(),
+ offsetInBytes, sizeInBytes, mAudioFormat,
+ writeMode == WRITE_BLOCKING);
+ } else {
+ ret = native_write_byte(NioUtils.unsafeArray(audioData),
+ NioUtils.unsafeArrayOffset(audioData) + audioData.position() + offsetInBytes,
+ sizeInBytes, mAudioFormat,
+ writeMode == WRITE_BLOCKING);
+ }
+
+ if ((mDataLoadMode == MODE_STATIC)
+ && (mState == STATE_NO_STATIC_DATA)
+ && (ret > 0)) {
+ // benign race with respect to other APIs that read mState
+ mState = STATE_INITIALIZED;
+ }
+
+ return ret;
+ }
+
+ /**
* Notifies the native resource to reuse the audio data already loaded in the native
* layer, that is to rewind to start of buffer.
* The track's creation mode must be {@link #MODE_STATIC}.
@@ -1339,11 +1436,15 @@
private native final void native_flush();
private native final int native_write_byte(byte[] audioData,
- int offsetInBytes, int sizeInBytes, int format);
+ int offsetInBytes, int sizeInBytes, int format,
+ boolean isBlocking);
private native final int native_write_short(short[] audioData,
int offsetInShorts, int sizeInShorts, int format);
+ private native final int native_write_native_bytes(Object audioData,
+ int positionInBytes, int offsetInBytes, int sizeInBytes, int format, boolean blocking);
+
private native final int native_reload_static();
private native final int native_get_native_frame_count();
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 2806bff..e20a4af 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -3069,7 +3069,7 @@
if (refreshTime ||
nanoTime >= mLastNanoTime + MAX_NS_WITHOUT_POSITION_CHECK) {
try {
- mLastTimeUs = mPlayer.getCurrentPosition() * 1000;
+ mLastTimeUs = mPlayer.getCurrentPosition() * 1000L;
mPaused = !mPlayer.isPlaying();
if (DEBUG) Log.v(TAG, (mPaused ? "paused" : "playing") + " at " + mLastTimeUs);
} catch (IllegalStateException e) {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
index 0fa27c1..14de6dd 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
@@ -58,8 +58,8 @@
// how many cells the user has to cross before we poke the wakelock
private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
- private int mFailedPatternAttemptsSinceLastTimeout = 0;
- private int mTotalFailedPatternAttempts = 0;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
private CountDownTimer mCountdownTimer = null;
private LockPatternUtils mLockPatternUtils;
private LockPatternView mLockPatternView;
@@ -100,6 +100,7 @@
public KeyguardPatternView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
}
public void setKeyguardCallback(KeyguardSecurityCallback callback) {
@@ -202,7 +203,8 @@
if (mCallback.isVerifyUnlockOnly()) {
updateFooter(FooterMode.VerifyUnlocked);
} else if (mEnableFallback &&
- (mTotalFailedPatternAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
+ (mKeyguardUpdateMonitor.getFailedUnlockAttempts()
+ >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
updateFooter(FooterMode.ForgotLockPattern);
} else {
updateFooter(FooterMode.Normal);
@@ -211,7 +213,7 @@
}
private void displayDefaultSecurityMessage() {
- if (KeyguardUpdateMonitor.getInstance(mContext).getMaxBiometricUnlockAttemptsReached()) {
+ if (mKeyguardUpdateMonitor.getMaxBiometricUnlockAttemptsReached()) {
mSecurityMessageDisplay.setMessage(R.string.faceunlock_multiple_failures, true);
} else {
mSecurityMessageDisplay.setMessage(R.string.kg_pattern_instructions, false);
@@ -262,20 +264,20 @@
if (mLockPatternUtils.checkPattern(pattern)) {
mCallback.reportUnlockAttempt(true);
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
- mTotalFailedPatternAttempts = 0;
mCallback.dismiss(true);
} else {
if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
}
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
- if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
- mTotalFailedPatternAttempts++;
- mFailedPatternAttemptsSinceLastTimeout++;
+ boolean registeredAttempt =
+ pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL;
+ if (registeredAttempt) {
mCallback.reportUnlockAttempt(false);
}
- if (mFailedPatternAttemptsSinceLastTimeout
- >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) {
+ int attempts = mKeyguardUpdateMonitor.getFailedUnlockAttempts();
+ if (registeredAttempt &&
+ 0 == (attempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
handleAttemptLockout(deadline);
} else {
@@ -363,7 +365,6 @@
mLockPatternView.setEnabled(true);
displayDefaultSecurityMessage();
// TODO mUnlockIcon.setVisibility(View.VISIBLE);
- mFailedPatternAttemptsSinceLastTimeout = 0;
if (mEnableFallback) {
updateFooter(FooterMode.ForgotLockPattern);
} else {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 8d6fe41..b09cc1d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -52,6 +52,7 @@
<uses-permission android:name="android.permission.START_ANY_ACTIVITY" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" />
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
<!-- WindowManager -->
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
@@ -140,6 +141,18 @@
</intent-filter>
</receiver>
+ <!-- Alternate Recents -->
+ <activity android:name=".recents.RecentsActivity"
+ android:launchMode="singleInstance"
+ android:excludeFromRecents="true"
+ android:theme="@style/RecentsTheme">
+ <intent-filter>
+ <action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
+ </intent-filter>
+ </activity>
+
+ <service android:name=".recents.RecentsService" />
+
<!-- started from UsbDeviceSettingsManager -->
<activity android:name=".usb.UsbConfirmActivity"
android:exported="true"
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 1ff93d2..48d9722 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -6,6 +6,11 @@
public void setGlowAlpha(float);
public void setGlowScale(float);
}
+-keep class com.android.systemui.recents.views.TaskIconView {
+ public void setCircularClipRadius(float);
+ public float getCircularClipRadius();
+}
-keep class com.android.systemui.statusbar.phone.PhoneStatusBar
-keep class com.android.systemui.statusbar.tv.TvStatusBar
+-keep class com.android.systemui.recents.*
\ No newline at end of file
diff --git a/packages/SystemUI/res/anim/recents_from_launcher_enter.xml b/packages/SystemUI/res/anim/recents_from_launcher_enter.xml
new file mode 100644
index 0000000..4bd7e82
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_from_launcher_enter.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false"
+ android:zAdjustment="top">
+ <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@android:interpolator/accelerate_cubic"
+ android:duration="250"/>
+</set>
diff --git a/packages/SystemUI/res/anim/recents_from_launcher_exit.xml b/packages/SystemUI/res/anim/recents_from_launcher_exit.xml
new file mode 100644
index 0000000..becc9d0
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_from_launcher_exit.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false"
+ android:zAdjustment="normal">
+ <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@android:interpolator/decelerate_cubic"
+ android:duration="250"/>
+</set>
diff --git a/packages/SystemUI/res/layout/recents_empty.xml b/packages/SystemUI/res/layout/recents_empty.xml
new file mode 100644
index 0000000..6268628
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_empty.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textSize="40sp"
+ android:textColor="#ffffffff"
+ android:text="@string/recents_empty_message"
+ android:fontFamily="sans-serif-thin"
+ android:visibility="gone" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 1e6011c..f0db449 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -198,7 +198,7 @@
<string name="quick_settings_wifi_not_connected" msgid="7171904845345573431">"Ikke forbundet"</string>
<string name="quick_settings_wifi_no_network" msgid="2221993077220856376">"Intet netværk"</string>
<string name="quick_settings_wifi_off_label" msgid="7558778100843885864">"Wi-Fi slået fra"</string>
- <string name="quick_settings_remote_display_no_connection_label" msgid="372107699274391290">"Cast-skærm"</string>
+ <string name="quick_settings_remote_display_no_connection_label" msgid="372107699274391290">"Cast skærm"</string>
<string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"Lysstyrke"</string>
<string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTO"</string>
<string name="quick_settings_inversion_label" msgid="1666358784283020762">"Farveinverteringstilstand"</string>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 94796af..ce05639 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -507,6 +507,9 @@
<!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] -->
<string name="quick_settings_color_space_label">Color correction mode</string>
+ <!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
+ <string name="recents_empty_message">RECENTS</string>
+
<!-- Glyph to be overlaid atop the battery when the level is extremely low. Do not translate. -->
<string name="battery_meter_very_low_overlay_symbol">!</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 54f03bd..14af020 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -20,6 +20,13 @@
<item name="android:windowAnimationStyle">@style/Animation.RecentsActivity</item>
</style>
+ <!-- Alternate Recents theme -->
+ <style name="RecentsTheme" parent="@android:style/Theme.Holo.Wallpaper.NoTitleBar">
+ <item name="android:windowTranslucentStatus">true</item>
+ <item name="android:windowTranslucentNavigation">true</item>
+ <item name="android:windowAnimationStyle">@style/Animation.RecentsActivity</item>
+ </style>
+
<!-- Animations for a non-full-screen window or activity. -->
<style name="Animation.RecentsActivity" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/recents_launch_from_launcher_enter</item>
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
index f5670e1..07c0c78 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
@@ -16,37 +16,143 @@
package com.android.systemui.recent;
+import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Matrix;
import android.graphics.Paint;
+import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
+import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.View;
-
+import android.view.WindowManager;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.SystemUI;
+import java.util.List;
+
+
public class Recents extends SystemUI implements RecentsComponent {
+ /** A handler for messages from the recents implementation */
+ class RecentsMessageHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ if (!mUseAlternateRecents) return;
+ if (msg.what == MSG_UPDATE_FOR_CONFIGURATION) {
+ Resources res = mContext.getResources();
+ float statusBarHeight = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+ mFirstTaskRect = (Rect) msg.getData().getParcelable("taskRect");
+ mFirstTaskRect.offset(0, (int) statusBarHeight);
+ }
+ }
+ }
+
+ /** A service connection to the recents implementation */
+ class RecentsServiceConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ if (!mUseAlternateRecents) return;
+
+ Log.d(TAG, "[RecentsComponent|ServiceConnection|onServiceConnected] toggleRecents: " +
+ mToggleRecentsUponServiceBound);
+ mService = new Messenger(service);
+ mServiceIsBound = true;
+
+ // Toggle recents if this service connection was triggered by hitting the recents button
+ if (mToggleRecentsUponServiceBound) {
+ startAlternateRecentsActivity();
+ }
+ mToggleRecentsUponServiceBound = false;
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ if (!mUseAlternateRecents) return;
+
+ Log.d(TAG, "[RecentsComponent|ServiceConnection|onServiceDisconnected]");
+ mService = null;
+ mServiceIsBound = false;
+ }
+ }
+
private static final String TAG = "Recents";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
+
+ final static int MSG_UPDATE_FOR_CONFIGURATION = 0;
+ final static int MSG_UPDATE_TASK_THUMBNAIL = 1;
+ final static int MSG_PRELOAD_TASKS = 2;
+ final static int MSG_CANCEL_PRELOAD_TASKS = 3;
+
+ final static String sToggleRecentsAction = "com.android.systemui.recents.TOGGLE_RECENTS";
+ final static String sRecentsPackage = "com.android.systemui";
+ final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
+ final static String sRecentsService = "com.android.systemui.recents.RecentsService";
+
+ // Which recents to use
+ boolean mUseAlternateRecents;
+
+ // Recents service binding
+ Messenger mService = null;
+ Messenger mMessenger;
+ boolean mServiceIsBound = false;
+ boolean mToggleRecentsUponServiceBound;
+ RecentsServiceConnection mConnection = new RecentsServiceConnection();
+
+ View mStatusBarView;
+ Rect mFirstTaskRect = new Rect();
+
+ public Recents() {
+ mMessenger = new Messenger(new RecentsMessageHandler());
+ }
@Override
public void start() {
+ mUseAlternateRecents =
+ SystemProperties.getBoolean("persist.recents.use_alternate", false);
+
putComponent(RecentsComponent.class, this);
+
+ if (mUseAlternateRecents) {
+ Log.d(TAG, "[RecentsComponent|start]");
+
+ // Try to create a long-running connection to the recents service
+ bindToRecentsService(false);
+ }
}
@Override
public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
+ if (mUseAlternateRecents) {
+ // Launch the alternate recents if required
+ toggleAlternateRecents(display, layoutDirection, statusBarView);
+ return;
+ }
+
if (DEBUG) Log.d(TAG, "toggle recents panel");
try {
TaskDescription firstTask = RecentTasksLoader.getInstance(mContext).getFirstTask();
@@ -190,33 +296,227 @@
}
}
+ /** Toggles the alternate recents activity */
+ public void toggleAlternateRecents(Display display, int layoutDirection, View statusBarView) {
+ if (!mUseAlternateRecents) return;
+
+ Log.d(TAG, "[RecentsComponent|toggleRecents] serviceIsBound: " + mServiceIsBound);
+ mStatusBarView = statusBarView;
+ if (!mServiceIsBound) {
+ // Try to create a long-running connection to the recents service before toggling
+ // recents
+ bindToRecentsService(true);
+ return;
+ }
+
+ try {
+ startAlternateRecentsActivity();
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "Failed to launch RecentAppsIntent", e);
+ }
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ if (mServiceIsBound) {
+ Resources res = mContext.getResources();
+ int statusBarHeight = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+ int navBarHeight = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_height);
+ Rect rect = new Rect();
+ WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getRectSize(rect);
+
+ // Try and update the recents configuration
+ try {
+ Bundle data = new Bundle();
+ data.putParcelable("windowRect", rect);
+ data.putParcelable("systemInsets", new Rect(0, statusBarHeight, 0, 0));
+ Message msg = Message.obtain(null, MSG_UPDATE_FOR_CONFIGURATION, 0, 0);
+ msg.setData(data);
+ msg.replyTo = mMessenger;
+ mService.send(msg);
+ } catch (RemoteException re) {
+ re.printStackTrace();
+ }
+ }
+ }
+
+ /** Binds to the recents implementation */
+ private void bindToRecentsService(boolean toggleRecentsUponConnection) {
+ if (!mUseAlternateRecents) return;
+
+ mToggleRecentsUponServiceBound = toggleRecentsUponConnection;
+ Intent intent = new Intent();
+ intent.setClassName(sRecentsPackage, sRecentsService);
+ mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ /** Loads the first task thumbnail */
+ Bitmap loadFirstTaskThumbnail() {
+ ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(1,
+ ActivityManager.RECENT_IGNORE_UNAVAILABLE, UserHandle.CURRENT.getIdentifier());
+ for (ActivityManager.RecentTaskInfo t : tasks) {
+ // Skip tasks in the home stack
+ if (am.isInHomeStack(t.persistentId)) {
+ return null;
+ }
+
+ Bitmap thumbnail = am.getTaskTopThumbnail(t.persistentId);
+ return thumbnail;
+ }
+ return null;
+ }
+
+ /** Returns whether there is a first task */
+ boolean hasFirstTask() {
+ ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(1,
+ ActivityManager.RECENT_IGNORE_UNAVAILABLE, UserHandle.CURRENT.getIdentifier());
+ for (ActivityManager.RecentTaskInfo t : tasks) {
+ // Skip tasks in the home stack
+ if (am.isInHomeStack(t.persistentId)) {
+ continue;
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ /** Converts from the device rotation to the degree */
+ float getDegreesForRotation(int value) {
+ switch (value) {
+ case Surface.ROTATION_90:
+ return 360f - 90f;
+ case Surface.ROTATION_180:
+ return 360f - 180f;
+ case Surface.ROTATION_270:
+ return 360f - 270f;
+ }
+ return 0f;
+ }
+
+ /** Takes a screenshot of the surface */
+ Bitmap takeScreenshot(Display display) {
+ DisplayMetrics dm = new DisplayMetrics();
+ display.getRealMetrics(dm);
+ float[] dims = {dm.widthPixels, dm.heightPixels};
+ float degrees = getDegreesForRotation(display.getRotation());
+ boolean requiresRotation = (degrees > 0);
+ if (requiresRotation) {
+ // Get the dimensions of the device in its native orientation
+ Matrix m = new Matrix();
+ m.preRotate(-degrees);
+ m.mapPoints(dims);
+ dims[0] = Math.abs(dims[0]);
+ dims[1] = Math.abs(dims[1]);
+ }
+ return SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
+ }
+
+ /** Starts the recents activity */
+ void startAlternateRecentsActivity() {
+ Rect taskRect = mFirstTaskRect;
+ if (taskRect != null && taskRect.width() > 0 && taskRect.height() > 0 && hasFirstTask()) {
+ // Loading from thumbnail
+ Bitmap thumbnail;
+ Bitmap firstThumbnail = loadFirstTaskThumbnail();
+ if (firstThumbnail == null) {
+ // Load the thumbnail from the screenshot
+ WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ Display display = wm.getDefaultDisplay();
+ Bitmap screenshot = takeScreenshot(display);
+ Resources res = mContext.getResources();
+ int size = Math.min(screenshot.getWidth(), screenshot.getHeight());
+ int statusBarHeight = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+ thumbnail = Bitmap.createBitmap(mFirstTaskRect.width(), mFirstTaskRect.height(),
+ Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(thumbnail);
+ c.drawBitmap(screenshot, new Rect(0, statusBarHeight, size, statusBarHeight + size),
+ new Rect(0, 0, mFirstTaskRect.width(), mFirstTaskRect.height()), null);
+ c.setBitmap(null);
+ // Recycle the old screenshot
+ screenshot.recycle();
+ } else {
+ // Create the thumbnail
+ thumbnail = Bitmap.createBitmap(mFirstTaskRect.width(), mFirstTaskRect.height(),
+ Bitmap.Config.ARGB_8888);
+ int size = Math.min(firstThumbnail.getWidth(), firstThumbnail.getHeight());
+ Canvas c = new Canvas(thumbnail);
+ c.drawBitmap(firstThumbnail, new Rect(0, 0, size, size),
+ new Rect(0, 0, mFirstTaskRect.width(), mFirstTaskRect.height()), null);
+ c.setBitmap(null);
+ // Recycle the old thumbnail
+ firstThumbnail.recycle();
+ }
+
+ ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView,
+ thumbnail, mFirstTaskRect.left, mFirstTaskRect.top, null);
+ startAlternateRecentsActivity(opts);
+ } else {
+ ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
+ R.anim.recents_from_launcher_enter,
+ R.anim.recents_from_launcher_exit);
+ startAlternateRecentsActivity(opts);
+ }
+ }
+
+ /** Starts the recents activity */
+ void startAlternateRecentsActivity(ActivityOptions opts) {
+ Intent intent = new Intent(sToggleRecentsAction);
+ intent.setClassName(sRecentsPackage, sRecentsActivity);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ if (opts != null) {
+ mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
+ UserHandle.USER_CURRENT));
+ } else {
+ mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ }
+ }
+
@Override
public void preloadRecentTasksList() {
- if (DEBUG) Log.d(TAG, "preloading recents");
- Intent intent = new Intent(RecentsActivity.PRELOAD_INTENT);
- intent.setClassName("com.android.systemui",
- "com.android.systemui.recent.RecentsPreloadReceiver");
- mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ if (mUseAlternateRecents) {
+ Log.d(TAG, "[RecentsComponent|preloadRecents]");
+ } else {
+ Intent intent = new Intent(RecentsActivity.PRELOAD_INTENT);
+ intent.setClassName("com.android.systemui",
+ "com.android.systemui.recent.RecentsPreloadReceiver");
+ mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
- RecentTasksLoader.getInstance(mContext).preloadFirstTask();
+ RecentTasksLoader.getInstance(mContext).preloadFirstTask();
+ }
}
@Override
public void cancelPreloadingRecentTasksList() {
- if (DEBUG) Log.d(TAG, "cancel preloading recents");
- Intent intent = new Intent(RecentsActivity.CANCEL_PRELOAD_INTENT);
- intent.setClassName("com.android.systemui",
- "com.android.systemui.recent.RecentsPreloadReceiver");
- mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ if (mUseAlternateRecents) {
+ Log.d(TAG, "[RecentsComponent|cancelPreload]");
+ } else {
+ Intent intent = new Intent(RecentsActivity.CANCEL_PRELOAD_INTENT);
+ intent.setClassName("com.android.systemui",
+ "com.android.systemui.recent.RecentsPreloadReceiver");
+ mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
- RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
+ RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
+ }
}
@Override
public void closeRecents() {
- if (DEBUG) Log.d(TAG, "closing recents panel");
- Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT);
- intent.setPackage("com.android.systemui");
- mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ if (mUseAlternateRecents) {
+ Log.d(TAG, "[RecentsComponent|closeRecents]");
+ } else {
+ Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT);
+ intent.setPackage("com.android.systemui");
+ mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+
+ RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Console.java b/packages/SystemUI/src/com/android/systemui/recents/Console.java
new file mode 100644
index 0000000..b3d9ccf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/Console.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents;
+
+
+import android.content.Context;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.widget.Toast;
+
+public class Console {
+ // Colors
+ public static final String AnsiReset = "\u001B[0m";
+ public static final String AnsiBlack = "\u001B[30m";
+ public static final String AnsiRed = "\u001B[31m"; // SystemUIHandshake
+ public static final String AnsiGreen = "\u001B[32m"; // MeasureAndLayout
+ public static final String AnsiYellow = "\u001B[33m"; // SynchronizeViewsWithModel
+ public static final String AnsiBlue = "\u001B[34m"; // TouchEvents
+ public static final String AnsiPurple = "\u001B[35m"; // Draw
+ public static final String AnsiCyan = "\u001B[36m"; // ClickEvents
+ public static final String AnsiWhite = "\u001B[37m";
+
+ /** Logs a key */
+ public static void log(String key) {
+ Console.log(true, key, "", AnsiReset);
+ }
+
+ /** Logs a conditioned key */
+ public static void log(boolean condition, String key) {
+ if (condition) {
+ Console.log(condition, key, "", AnsiReset);
+ }
+ }
+
+ /** Logs a key in a specific color */
+ public static void log(boolean condition, String key, Object data) {
+ if (condition) {
+ Console.log(condition, key, data, AnsiReset);
+ }
+ }
+
+ /** Logs a key with data in a specific color */
+ public static void log(boolean condition, String key, Object data, String color) {
+ if (condition) {
+ System.out.println(color + key + AnsiReset + " " + data.toString());
+ }
+ }
+
+ /** Logs an error */
+ public static void logError(Context context, String msg) {
+ Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
+ Log.e("Recents", msg);
+ }
+
+ /** Logs a divider bar */
+ public static void logDivider(boolean condition) {
+ if (condition) {
+ System.out.println("==== [" + System.currentTimeMillis() +
+ "] ============================================================");
+ }
+ }
+
+ /** Returns the stringified MotionEvent action */
+ public static String motionEventActionToString(int action) {
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ return "Down";
+ case MotionEvent.ACTION_UP:
+ return "Up";
+ case MotionEvent.ACTION_MOVE:
+ return "Move";
+ case MotionEvent.ACTION_CANCEL:
+ return "Cancel";
+ case MotionEvent.ACTION_POINTER_DOWN:
+ return "Pointer Down";
+ case MotionEvent.ACTION_POINTER_UP:
+ return "Pointer Up";
+ default:
+ return "" + action;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
new file mode 100644
index 0000000..aeae4ab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents;
+
+/**
+ * Constants
+ * XXX: We are going to move almost all of these into a resource.
+ */
+public class Constants {
+ public static class DebugFlags {
+ // Enable this with any other debug flag to see more info
+ public static final boolean Verbose = false;
+
+ public static class App {
+ public static final boolean EnableTaskFiltering = false;
+ public static final boolean EnableTaskStackClipping = false;
+ public static final boolean EnableBackgroundTaskLoading = true;
+ public static final boolean ForceDisableBackgroundCache = false;
+ public static final boolean TaskDataLoader = false;
+ public static final boolean SystemUIHandshake = false;
+ public static final boolean TimeSystemCalls = false;
+ }
+
+ public static class UI {
+ public static final boolean Draw = false;
+ public static final boolean ClickEvents = false;
+ public static final boolean TouchEvents = false;
+ public static final boolean MeasureAndLayout = false;
+ public static final boolean Clipping = false;
+ public static final boolean HwLayers = true;
+ }
+
+ public static class TaskStack {
+ public static final boolean SynchronizeViewsWithModel = false;
+ }
+
+ public static class ViewPool {
+ public static final boolean PoolCallbacks = false;
+ }
+ }
+
+ public static class Values {
+ public static class Window {
+ public static final float DarkBackgroundDim = 0.5f;
+ public static final float BackgroundDim = 0.35f;
+ }
+
+ public static class RecentsTaskLoader {
+ // XXX: This should be calculated on the first load
+ public static final int PreloadFirstTasksCount = 5;
+ public static final int TaskEntryMultiplier = 1;
+ }
+
+ public static class TaskStackView {
+ public static class Animation {
+ public static final int TaskRemovedReshuffleDuration = 200;
+ public static final int SnapScrollBackDuration = 650;
+ public static final int SwipeDismissDuration = 350;
+ public static final int SwipeSnapBackDuration = 350;
+ }
+
+ // The padding will be applied to the smallest dimension, and then applied to all sides
+ public static final float StackPaddingPct = 0.15f;
+ // The overlap height relative to the task height
+ public static final float StackOverlapPct = 0.65f;
+ // The height of the peek space relative to the stack height
+ public static final float StackPeekHeightPct = 0.1f;
+ // The min scale of the last card in the peek area
+ public static final float StackPeekMinScale = 0.9f;
+ // The number of cards we see in the peek space
+ public static final int StackPeekNumCards = 3;
+ }
+
+ public static class TaskView {
+ public static class Animation {
+ public static final int TaskDataUpdatedFadeDuration = 250;
+ public static final int TaskIconCircularClipInDuration = 225;
+ public static final int TaskIconCircularClipOutDuration = 85;
+ }
+
+ public static final boolean AnimateFrontTaskIconOnEnterRecents = true;
+ public static final boolean AnimateFrontTaskIconOnLeavingRecents = true;
+ public static final boolean AnimateFrontTaskIconOnLeavingUseClip = false;
+ public static final boolean DrawColoredTaskBars = false;
+ public static final boolean UseRoundedCorners = true;
+ public static final float RoundedCornerRadiusDps = 3;
+
+ public static final float TaskBarHeightDps = 54;
+ public static final float TaskIconSizeDps = 60;
+ }
+ }
+
+ // UNMIGRATED CONSTANTS:
+
+ /** Determines whether to layout the stack vertically in landscape mode */
+ public static final boolean LANDSCAPE_LAYOUT_VERTICAL_STACK = true;
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
new file mode 100644
index 0000000..d050847
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import com.android.systemui.recents.model.SpaceNode;
+import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.RecentsView;
+import com.android.systemui.R;
+
+import java.util.ArrayList;
+
+
+/* Activity */
+public class RecentsActivity extends Activity {
+ FrameLayout mContainerView;
+ RecentsView mRecentsView;
+ View mEmptyView;
+ boolean mVisible;
+
+ /** Updates the set of recent tasks */
+ void updateRecentsTasks() {
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ SpaceNode root = loader.reload(this, Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount);
+ ArrayList<TaskStack> stacks = root.getStacks();
+ if (!stacks.isEmpty()) {
+ // XXX: We just replace the root every time for now, we will change this in the future
+ mRecentsView.setBSP(root);
+ }
+
+ // Add the default no-recents layout
+ if (stacks.size() == 1 && stacks.get(0).getTaskCount() == 0) {
+ mEmptyView.setVisibility(View.VISIBLE);
+
+ // Dim the background even more
+ WindowManager.LayoutParams wlp = getWindow().getAttributes();
+ wlp.dimAmount = Constants.Values.Window.DarkBackgroundDim;
+ getWindow().setAttributes(wlp);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ } else {
+ mEmptyView.setVisibility(View.GONE);
+ }
+ }
+
+ /** Dismisses recents if we are already visible and the intent is to toggle the recents view */
+ boolean dismissRecentsIfVisible(Intent intent) {
+ if ("com.android.systemui.recents.TOGGLE_RECENTS".equals(intent.getAction())) {
+ if (mVisible) {
+ if (!mRecentsView.launchFirstTask()) {
+ finish();
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Called with the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Console.logDivider(Constants.DebugFlags.App.SystemUIHandshake);
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsActivity|onCreate]",
+ getIntent().getAction() + " visible: " + mVisible, Console.AnsiRed);
+
+ // Initialize the loader and the configuration
+ RecentsTaskLoader.initialize(this);
+ RecentsConfiguration.reinitialize(this);
+
+ // Dismiss recents if it is visible and we are toggling
+ if (dismissRecentsIfVisible(getIntent())) return;
+
+ // Set the background dim
+ WindowManager.LayoutParams wlp = getWindow().getAttributes();
+ wlp.dimAmount = Constants.Values.Window.BackgroundDim;
+ getWindow().setAttributes(wlp);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+
+ // Create the view hierarchy
+ mRecentsView = new RecentsView(this);
+ mRecentsView.setLayoutParams(new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT));
+
+ // Create the empty view
+ LayoutInflater inflater = LayoutInflater.from(this);
+ mEmptyView = inflater.inflate(R.layout.recents_empty, mContainerView, false);
+
+ mContainerView = new FrameLayout(this);
+ mContainerView.addView(mRecentsView);
+ mContainerView.addView(mEmptyView);
+ setContentView(mContainerView);
+
+ // Update the recent tasks
+ updateRecentsTasks();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ Console.logDivider(Constants.DebugFlags.App.SystemUIHandshake);
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsActivity|onNewIntent]",
+ intent.getAction() + " visible: " + mVisible, Console.AnsiRed);
+
+ // Dismiss recents if it is visible and we are toggling
+ if (dismissRecentsIfVisible(intent)) return;
+
+ // Initialize the loader and the configuration
+ RecentsTaskLoader.initialize(this);
+ RecentsConfiguration.reinitialize(this);
+
+ // Update the recent tasks
+ updateRecentsTasks();
+ }
+
+ @Override
+ protected void onStart() {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsActivity|onStart]", "",
+ Console.AnsiRed);
+ super.onStart();
+ mVisible = true;
+ }
+
+ @Override
+ protected void onResume() {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsActivity|onResume]", "",
+ Console.AnsiRed);
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsActivity|onPause]", "",
+ Console.AnsiRed);
+ super.onPause();
+
+ // Stop the loader when we leave Recents
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ loader.stopLoader();
+ }
+
+ @Override
+ protected void onStop() {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsActivity|onStop]", "",
+ Console.AnsiRed);
+ super.onStop();
+ mVisible = false;
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (!mRecentsView.unfilterFilteredStacks()) {
+ super.onBackPressed();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
new file mode 100644
index 0000000..f3881ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+
+
+/** A static Recents configuration for the current context
+ * NOTE: We should not hold any references to a Context from a static instance */
+public class RecentsConfiguration {
+ static RecentsConfiguration sInstance;
+
+ DisplayMetrics mDisplayMetrics;
+
+ public boolean layoutVerticalStack;
+ public Rect systemInsets = new Rect();
+
+ /** Private constructor */
+ private RecentsConfiguration() {}
+
+ /** Updates the configuration to the current context */
+ public static RecentsConfiguration reinitialize(Context context) {
+ if (sInstance == null) {
+ sInstance = new RecentsConfiguration();
+ }
+ sInstance.update(context);
+ return sInstance;
+ }
+
+ /** Returns the current recents configuration */
+ public static RecentsConfiguration getInstance() {
+ return sInstance;
+ }
+
+ /** Updates the state, given the specified context */
+ void update(Context context) {
+ mDisplayMetrics = context.getResources().getDisplayMetrics();
+
+ boolean isPortrait = context.getResources().getConfiguration().orientation ==
+ Configuration.ORIENTATION_PORTRAIT;
+ layoutVerticalStack = isPortrait || Constants.LANDSCAPE_LAYOUT_VERTICAL_STACK;
+ }
+
+ public void updateSystemInsets(Rect insets) {
+ systemInsets.set(insets);
+ }
+
+ /** Converts from DPs to PXs */
+ public int pxFromDp(float size) {
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ size, mDisplayMetrics));
+ }
+ /** Converts from SPs to PXs */
+ public int pxFromSp(float size) {
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
+ size, mDisplayMetrics));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
new file mode 100644
index 0000000..522ab0f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.TaskStackView;
+import com.android.systemui.recents.views.TaskViewTransform;
+
+
+/* Service */
+public class RecentsService extends Service {
+ // XXX: This should be getting the message from recents definition
+ final static int MSG_UPDATE_RECENTS_FOR_CONFIGURATION = 0;
+
+ class MessageHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsService|handleMessage]", msg);
+ if (msg.what == MSG_UPDATE_RECENTS_FOR_CONFIGURATION) {
+ Context context = RecentsService.this;
+ RecentsTaskLoader.initialize(context);
+ RecentsConfiguration.reinitialize(context);
+
+ try {
+ Bundle data = msg.getData();
+ Rect windowRect = (Rect) data.getParcelable("windowRect");
+ Rect systemInsets = (Rect) data.getParcelable("systemInsets");
+ RecentsConfiguration.getInstance().updateSystemInsets(systemInsets);
+
+ // Create a dummy task stack & compute the rect for the thumbnail to animate to
+ TaskStack stack = new TaskStack(context);
+ TaskStackView tsv = new TaskStackView(context, stack);
+ tsv.computeRects(windowRect.width(), windowRect.height() - systemInsets.top);
+ tsv.boundScroll();
+ TaskViewTransform transform = tsv.getStackTransform(0);
+
+ data.putParcelable("taskRect", transform.rect);
+ Message reply = Message.obtain(null, MSG_UPDATE_RECENTS_FOR_CONFIGURATION, 0, 0);
+ reply.setData(data);
+ msg.replyTo.send(reply);
+ } catch (RemoteException re) {
+ re.printStackTrace();
+ }
+ }
+ }
+ }
+
+ Messenger mMessenger = new Messenger(new MessageHandler());
+
+ @Override
+ public void onCreate() {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsService|onCreate]");
+ super.onCreate();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsService|onBind]");
+ return mMessenger.getBinder();
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsService|onUnbind]");
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public void onRebind(Intent intent) {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsService|onRebind]");
+ super.onRebind(intent);
+ }
+
+ @Override
+ public void onDestroy() {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsService|onDestroy]");
+ super.onDestroy();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
new file mode 100644
index 0000000..c303ca7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
@@ -0,0 +1,463 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.UserHandle;
+import android.util.LruCache;
+import com.android.systemui.recents.model.SpaceNode;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+
+/** A bitmap load queue */
+class TaskResourceLoadQueue {
+ ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
+
+ Task nextTask() {
+ Console.log(Constants.DebugFlags.App.TaskDataLoader, " [TaskResourceLoadQueue|nextTask]");
+ return mQueue.poll();
+ }
+
+ void addTask(Task t) {
+ Console.log(Constants.DebugFlags.App.TaskDataLoader, " [TaskResourceLoadQueue|addTask]");
+ if (!mQueue.contains(t)) {
+ mQueue.add(t);
+ }
+ synchronized(this) {
+ notifyAll();
+ }
+ }
+
+ void removeTask(Task t) {
+ Console.log(Constants.DebugFlags.App.TaskDataLoader, " [TaskResourceLoadQueue|removeTask]");
+ mQueue.remove(t);
+ }
+
+ void clearTasks() {
+ Console.log(Constants.DebugFlags.App.TaskDataLoader, " [TaskResourceLoadQueue|clearTasks]");
+ mQueue.clear();
+ }
+
+ boolean isEmpty() {
+ return mQueue.isEmpty();
+ }
+}
+
+/* Task resource loader */
+class TaskResourceLoader implements Runnable {
+ Context mContext;
+ HandlerThread mLoadThread;
+ Handler mLoadThreadHandler;
+ Handler mMainThreadHandler;
+
+ TaskResourceLoadQueue mLoadQueue;
+ DrawableLruCache mIconCache;
+ BitmapLruCache mThumbnailCache;
+ boolean mCancelled;
+
+ /** Constructor, creates a new loading thread that loads task resources in the background */
+ public TaskResourceLoader(TaskResourceLoadQueue loadQueue, DrawableLruCache iconCache,
+ BitmapLruCache thumbnailCache) {
+ mLoadQueue = loadQueue;
+ mIconCache = iconCache;
+ mThumbnailCache = thumbnailCache;
+ mMainThreadHandler = new Handler();
+ mLoadThread = new HandlerThread("Recents-TaskResourceLoader");
+ mLoadThread.setPriority(Thread.NORM_PRIORITY - 1);
+ mLoadThread.start();
+ mLoadThreadHandler = new Handler(mLoadThread.getLooper());
+ mLoadThreadHandler.post(this);
+ }
+
+ /** Restarts the loader thread */
+ void start(Context context) {
+ Console.log(Constants.DebugFlags.App.TaskDataLoader, "[TaskResourceLoader|start]");
+ mContext = context;
+ mCancelled = false;
+ // Notify the load thread to start loading
+ synchronized(mLoadThread) {
+ mLoadThread.notifyAll();
+ }
+ }
+
+ /** Requests the loader thread to stop after the current iteration */
+ void stop() {
+ Console.log(Constants.DebugFlags.App.TaskDataLoader, "[TaskResourceLoader|stop]");
+ // Mark as cancelled for the thread to pick up
+ mCancelled = true;
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ Console.log(Constants.DebugFlags.App.TaskDataLoader,
+ "[TaskResourceLoader|run|" + Thread.currentThread().getId() + "]");
+ if (mCancelled) {
+ // We have to unset the context here, since the background thread may be using it
+ // when we call stop()
+ mContext = null;
+ // If we are cancelled, then wait until we are started again
+ synchronized(mLoadThread) {
+ try {
+ Console.log(Constants.DebugFlags.App.TaskDataLoader,
+ "[TaskResourceLoader|waitOnLoadThreadCancelled]");
+ mLoadThread.wait();
+ } catch (InterruptedException ie) {
+ ie.printStackTrace();
+ }
+ }
+ } else {
+ // Load the next item from the queue
+ final Task t = mLoadQueue.nextTask();
+ if (t != null) {
+ try {
+ Drawable cachedIcon = mIconCache.get(t);
+ Bitmap cachedThumbnail = mThumbnailCache.get(t);
+ Console.log(Constants.DebugFlags.App.TaskDataLoader,
+ " [TaskResourceLoader|load]",
+ t + " icon: " + cachedIcon + " thumbnail: " + cachedThumbnail);
+ // Load the icon
+ if (cachedIcon == null) {
+ PackageManager pm = mContext.getPackageManager();
+ ActivityInfo info = pm.getActivityInfo(t.intent.getComponent(),
+ PackageManager.GET_META_DATA);
+ Drawable icon = info.loadIcon(pm);
+ if (!mCancelled) {
+ Console.log(Constants.DebugFlags.App.TaskDataLoader,
+ " [TaskResourceLoader|loadIcon]",
+ icon);
+ t.icon = icon;
+ mIconCache.put(t, icon);
+ }
+ }
+ // Load the thumbnail
+ if (cachedThumbnail == null) {
+ ActivityManager am = (ActivityManager)
+ mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ Bitmap thumbnail = am.getTaskTopThumbnail(t.id);
+ if (!mCancelled) {
+ if (thumbnail != null) {
+ Console.log(Constants.DebugFlags.App.TaskDataLoader,
+ " [TaskResourceLoader|loadThumbnail]",
+ thumbnail);
+ t.thumbnail = thumbnail;
+ mThumbnailCache.put(t, thumbnail);
+ } else {
+ Console.logError(mContext,
+ "Failed to load task top thumbnail for: " +
+ t.intent.getComponent().getPackageName());
+ }
+ }
+ }
+ if (!mCancelled) {
+ // Notify that the task data has changed
+ mMainThreadHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ t.notifyTaskDataChanged();
+ }
+ });
+ }
+ } catch (PackageManager.NameNotFoundException ne) {
+ ne.printStackTrace();
+ }
+ }
+
+ // If there are no other items in the list, then just wait until something is added
+ if (!mCancelled && mLoadQueue.isEmpty()) {
+ synchronized(mLoadQueue) {
+ try {
+ Console.log(Constants.DebugFlags.App.TaskDataLoader,
+ "[TaskResourceLoader|waitOnLoadQueue]");
+ mLoadQueue.wait();
+ } catch (InterruptedException ie) {
+ ie.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/** The drawable cache */
+class DrawableLruCache extends LruCache<Task, Drawable> {
+ public DrawableLruCache(int cacheSize) {
+ super(cacheSize);
+ }
+
+ @Override
+ protected int sizeOf(Task t, Drawable d) {
+ // The cache size will be measured in kilobytes rather than number of items
+ // NOTE: this isn't actually correct, as the icon may be smaller
+ int maxBytes = (d.getIntrinsicWidth() * d.getIntrinsicHeight() * 4);
+ return maxBytes / 1024;
+ }
+}
+
+/** The bitmap cache */
+class BitmapLruCache extends LruCache<Task, Bitmap> {
+ public BitmapLruCache(int cacheSize) {
+ super(cacheSize);
+ }
+
+ @Override
+ protected int sizeOf(Task t, Bitmap bitmap) {
+ // The cache size will be measured in kilobytes rather than number of items
+ return bitmap.getByteCount() / 1024;
+ }
+}
+
+/* Recents task loader
+ * NOTE: We should not hold any references to a Context from a static instance */
+public class RecentsTaskLoader {
+ static RecentsTaskLoader sInstance;
+
+ DrawableLruCache mIconCache;
+ BitmapLruCache mThumbnailCache;
+ TaskResourceLoadQueue mLoadQueue;
+ TaskResourceLoader mLoader;
+
+ BitmapDrawable mDefaultIcon;
+ Bitmap mDefaultThumbnail;
+
+ /** Private Constructor */
+ private RecentsTaskLoader(Context context) {
+ int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
+ int iconCacheSize = Constants.DebugFlags.App.ForceDisableBackgroundCache ? 1 : maxMemory / 16;
+ int thumbnailCacheSize = Constants.DebugFlags.App.ForceDisableBackgroundCache ? 1 : maxMemory / 8;
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake,
+ "[RecentsTaskLoader|init]", "thumbnailCache: " + thumbnailCacheSize +
+ " iconCache: " + iconCacheSize);
+ mLoadQueue = new TaskResourceLoadQueue();
+ mIconCache = new DrawableLruCache(iconCacheSize);
+ mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
+ mLoader = new TaskResourceLoader(mLoadQueue, mIconCache, mThumbnailCache);
+
+ // Create the default assets
+ Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas();
+ c.setBitmap(icon);
+ c.drawColor(0x00000000);
+ c.setBitmap(mDefaultThumbnail);
+ c.drawColor(0x00000000);
+ c.setBitmap(null);
+ mDefaultIcon = new BitmapDrawable(context.getResources(), icon);
+ Console.log(Constants.DebugFlags.App.TaskDataLoader,
+ "[RecentsTaskLoader|defaultBitmaps]",
+ "icon: " + mDefaultIcon + " thumbnail: " + mDefaultThumbnail, Console.AnsiRed);
+ }
+
+ /** Initializes the recents task loader */
+ public static RecentsTaskLoader initialize(Context context) {
+ if (sInstance == null) {
+ sInstance = new RecentsTaskLoader(context);
+ }
+ return sInstance;
+ }
+
+ /** Returns the current recents task loader */
+ public static RecentsTaskLoader getInstance() {
+ return sInstance;
+ }
+
+ /** Reload the set of recent tasks */
+ SpaceNode reload(Context context, int preloadCount) {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsTaskLoader|reload]");
+ TaskStack stack = new TaskStack(context);
+ SpaceNode root = new SpaceNode(context);
+ root.setStack(stack);
+ try {
+ long t1 = System.currentTimeMillis();
+
+ PackageManager pm = context.getPackageManager();
+ ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+
+ // Get the recent tasks
+ List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(25,
+ ActivityManager.RECENT_IGNORE_UNAVAILABLE, UserHandle.CURRENT.getIdentifier());
+ Collections.reverse(tasks);
+ Console.log(Constants.DebugFlags.App.TimeSystemCalls,
+ "[RecentsTaskLoader|getRecentTasks]",
+ "" + (System.currentTimeMillis() - t1) + "ms");
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake,
+ "[RecentsTaskLoader|tasks]", "" + tasks.size());
+
+ // Remove home/recents tasks
+ Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator();
+ while (iter.hasNext()) {
+ ActivityManager.RecentTaskInfo t = iter.next();
+
+ // Skip tasks in the home stack
+ if (am.isInHomeStack(t.persistentId)) {
+ iter.remove();
+ continue;
+ }
+ // Skip tasks from this Recents package
+ if (t.baseIntent.getComponent().getPackageName().equals(context.getPackageName())) {
+ iter.remove();
+ continue;
+ }
+ }
+
+ // Add each task to the task stack
+ t1 = System.currentTimeMillis();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ ActivityManager.RecentTaskInfo t = tasks.get(i);
+
+ // Load the label, icon and thumbnail
+ ActivityInfo info = pm.getActivityInfo(t.baseIntent.getComponent(),
+ PackageManager.GET_META_DATA);
+ String title = info.loadLabel(pm).toString();
+ Drawable icon = null;
+ Bitmap thumbnail = null;
+ Task task;
+ if (i >= (taskCount - preloadCount) || !Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake,
+ "[RecentsTaskLoader|preloadTask]",
+ "i: " + i + " task: " + t.baseIntent.getComponent().getPackageName());
+ icon = info.loadIcon(pm);
+ thumbnail = am.getTaskTopThumbnail(t.id);
+ for (int j = 0; j < Constants.Values.RecentsTaskLoader.TaskEntryMultiplier; j++) {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake,
+ " [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName());
+ task = new Task(t.persistentId, t.baseIntent, title, icon, thumbnail);
+ if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
+ if (thumbnail != null) mThumbnailCache.put(task, thumbnail);
+ if (icon != null) {
+ mIconCache.put(task, icon);
+ }
+ }
+ stack.addTask(task);
+ }
+ } else {
+ for (int j = 0; j < Constants.Values.RecentsTaskLoader.TaskEntryMultiplier; j++) {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake,
+ " [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName());
+ task = new Task(t.persistentId, t.baseIntent, title, null, null);
+ stack.addTask(task);
+ }
+ }
+
+ /*
+ if (stacks.containsKey(t.stackId)) {
+ builder = stacks.get(t.stackId);
+ } else {
+ builder = new TaskStackBuilder();
+ stacks.put(t.stackId, builder);
+ }
+ */
+ }
+ Console.log(Constants.DebugFlags.App.TimeSystemCalls,
+ "[RecentsTaskLoader|getAllTaskTopThumbnail]",
+ "" + (System.currentTimeMillis() - t1) + "ms");
+
+ /*
+ // Get all the stacks
+ t1 = System.currentTimeMillis();
+ List<ActivityManager.StackInfo> stackInfos = ams.getAllStackInfos();
+ Console.log(Constants.DebugFlags.App.TimeSystemCalls, "[RecentsTaskLoader|getAllStackInfos]", "" + (System.currentTimeMillis() - t1) + "ms");
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsTaskLoader|stacks]", "" + tasks.size());
+ for (ActivityManager.StackInfo s : stackInfos) {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake, " [RecentsTaskLoader|stack]", s.toString());
+ if (stacks.containsKey(s.stackId)) {
+ stacks.get(s.stackId).setRect(s.bounds);
+ }
+ }
+ */
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ mLoader.start(context);
+ return root;
+ }
+
+ /** Acquires the task resource data from the pool.
+ * XXX: Move this into Task? */
+ public void loadTaskData(Task t) {
+ if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
+ t.icon = mIconCache.get(t);
+ t.thumbnail = mThumbnailCache.get(t);
+
+ Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|loadTask]",
+ t + " icon: " + t.icon + " thumbnail: " + t.thumbnail);
+
+ boolean requiresLoad = false;
+ if (t.icon == null) {
+ t.icon = mDefaultIcon;
+ requiresLoad = true;
+ }
+ if (t.thumbnail == null) {
+ t.thumbnail = mDefaultThumbnail;
+ requiresLoad = true;
+ }
+ if (requiresLoad) {
+ mLoadQueue.addTask(t);
+ }
+ }
+ }
+
+ /** Releases the task resource data back into the pool.
+ * XXX: Move this into Task? */
+ public void unloadTaskData(Task t) {
+ if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
+ Console.log(Constants.DebugFlags.App.TaskDataLoader,
+ "[RecentsTaskLoader|unloadTask]", t);
+ mLoadQueue.removeTask(t);
+ t.icon = mDefaultIcon;
+ t.thumbnail = mDefaultThumbnail;
+ }
+ }
+
+ /** Completely removes the resource data from the pool.
+ * XXX: Move this into Task? */
+ public void deleteTaskData(Task t) {
+ if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
+ Console.log(Constants.DebugFlags.App.TaskDataLoader,
+ "[RecentsTaskLoader|deleteTask]", t);
+ mLoadQueue.removeTask(t);
+ mThumbnailCache.remove(t);
+ mIconCache.remove(t);
+ }
+ t.icon = mDefaultIcon;
+ t.thumbnail = mDefaultThumbnail;
+ }
+
+ /** Stops the task loader */
+ void stopLoader() {
+ Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|stopLoader]");
+ mLoader.stop();
+ mLoadQueue.clearTasks();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
new file mode 100644
index 0000000..33e4246
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents;
+
+import android.graphics.Rect;
+
+/* Common code */
+public class Utilities {
+ public static final Rect tmpRect = new Rect();
+ public static final Rect tmpRect2 = new Rect();
+
+ /** Scales a rect about its centroid */
+ public static void scaleRectAboutCenter(Rect r, float scale) {
+ if (scale != 1.0f) {
+ int cx = r.centerX();
+ int cy = r.centerY();
+ r.offset(-cx, -cy);
+ r.left = (int) (r.left * scale + 0.5f);
+ r.top = (int) (r.top * scale + 0.5f);
+ r.right = (int) (r.right * scale + 0.5f);
+ r.bottom = (int) (r.bottom * scale + 0.5f);
+ r.offset(cx, cy);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
new file mode 100644
index 0000000..5893abc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents.model;
+
+import android.content.Context;
+
+import java.util.ArrayList;
+
+
+/**
+ * The full recents space is partitioned using a BSP into various nodes that define where task
+ * stacks should be placed.
+ */
+public class SpaceNode {
+ Context mContext;
+
+ SpaceNode mStartNode;
+ SpaceNode mEndNode;
+
+ TaskStack mStack;
+
+ public SpaceNode(Context context) {
+ mContext = context;
+ }
+
+ /** Sets the current stack for this space node */
+ public void setStack(TaskStack stack) {
+ mStack = stack;
+ }
+
+ /** Returns the task stack (not null if this is a leaf) */
+ TaskStack getStack() {
+ return mStack;
+ }
+
+ /** Returns whether this is a leaf node */
+ boolean isLeafNode() {
+ return (mStartNode == null) && (mEndNode == null);
+ }
+
+ /** Returns all the descendent task stacks */
+ private void getStacksRec(ArrayList<TaskStack> stacks) {
+ if (isLeafNode()) {
+ stacks.add(mStack);
+ } else {
+ mStartNode.getStacksRec(stacks);
+ mEndNode.getStacksRec(stacks);
+ }
+ }
+ public ArrayList<TaskStack> getStacks() {
+ ArrayList<TaskStack> stacks = new ArrayList<TaskStack>();
+ getStacksRec(stacks);
+ return stacks;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNodeCallbacks.java b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNodeCallbacks.java
new file mode 100644
index 0000000..31b02e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNodeCallbacks.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents.model;
+
+import android.graphics.Rect;
+
+
+/* BSP node callbacks */
+public interface SpaceNodeCallbacks {
+ /** Notifies when a node is added */
+ public void onSpaceNodeAdded(SpaceNode node);
+ /** Notifies when a node is measured */
+ public void onSpaceNodeMeasured(SpaceNode node, Rect rect);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
new file mode 100644
index 0000000..9b03c5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents.model;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import com.android.systemui.recents.Constants;
+
+
+/**
+ * A task represents the top most task in the system's task stack.
+ */
+public class Task {
+ public final int id;
+ public final Intent intent;
+ public String title;
+ public Drawable icon;
+ public Bitmap thumbnail;
+
+ TaskCallbacks mCb;
+
+ public Task(int id, Intent intent, String activityTitle, Drawable icon, Bitmap thumbnail) {
+ this.id = id;
+ this.intent = intent;
+ this.title = activityTitle;
+ this.icon = icon;
+ this.thumbnail = thumbnail;
+ }
+
+ /** Set the callbacks */
+ public void setCallbacks(TaskCallbacks cb) {
+ mCb = cb;
+ }
+
+ /** Notifies the callback listeners that this task's data has changed */
+ public void notifyTaskDataChanged() {
+ if (mCb != null) {
+ mCb.onTaskDataChanged(this);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ // If we have multiple task entries for the same task, then we do the simple object
+ // equality check
+ if (Constants.Values.RecentsTaskLoader.TaskEntryMultiplier > 1) {
+ return super.equals(o);
+ }
+
+ // Otherwise, check that the id and intent match (the other fields can be asynchronously
+ // loaded and is unsuitable to testing the identity of this Task)
+ Task t = (Task) o;
+ return (id == t.id) &&
+ (intent.equals(t.intent));
+ }
+
+ @Override
+ public String toString() {
+ return "Task: " + intent.getComponent().getPackageName() + " [" + super.toString() + "]";
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskCallbacks.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskCallbacks.java
new file mode 100644
index 0000000..169f56c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskCallbacks.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents.model;
+
+/* Task callbacks */
+public interface TaskCallbacks {
+ /* Notifies when a task's data has been updated */
+ public void onTaskDataChanged(Task task);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
new file mode 100644
index 0000000..a5aa387
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -0,0 +1,231 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents.model;
+
+import android.content.Context;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * An interface for a task filter to query whether a particular task should show in a stack.
+ */
+interface TaskFilter {
+ /** Returns whether the filter accepts the specified task */
+ public boolean acceptTask(Task t, int index);
+}
+
+/**
+ * A list of filtered tasks.
+ */
+class FilteredTaskList {
+ ArrayList<Task> mTasks = new ArrayList<Task>();
+ ArrayList<Task> mFilteredTasks = new ArrayList<Task>();
+ TaskFilter mFilter;
+
+ /** Sets the task filter, saving the current touch state */
+ void setFilter(TaskFilter filter) {
+ mFilter = filter;
+ updateFilteredTasks();
+ }
+
+ /** Removes the task filter and returns the previous touch state */
+ void removeFilter() {
+ mFilter = null;
+ updateFilteredTasks();
+ }
+
+ /** Adds a new task to the task list */
+ void add(Task t) {
+ mTasks.add(t);
+ updateFilteredTasks();
+ }
+
+ /** Sets the list of tasks */
+ void set(List<Task> tasks) {
+ mTasks.clear();
+ mTasks.addAll(tasks);
+ updateFilteredTasks();
+ }
+
+ /** Removes a task from the base list only if it is in the filtered list */
+ boolean remove(Task t) {
+ if (mFilteredTasks.contains(t)) {
+ boolean removed = mTasks.remove(t);
+ updateFilteredTasks();
+ return removed;
+ }
+ return false;
+ }
+
+ /** Returns the index of this task in the list of filtered tasks */
+ int indexOf(Task t) {
+ return mFilteredTasks.indexOf(t);
+ }
+
+ /** Returns the size of the list of filtered tasks */
+ int size() {
+ return mFilteredTasks.size();
+ }
+
+ /** Returns whether the filtered list contains this task */
+ boolean contains(Task t) {
+ return mFilteredTasks.contains(t);
+ }
+
+ /** Updates the list of filtered tasks whenever the base task list changes */
+ private void updateFilteredTasks() {
+ mFilteredTasks.clear();
+ if (mFilter != null) {
+ int taskCount = mTasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task t = mTasks.get(i);
+ if (mFilter.acceptTask(t, i)) {
+ mFilteredTasks.add(t);
+ }
+ }
+ } else {
+ mFilteredTasks.addAll(mTasks);
+ }
+ }
+
+ /** Returns whether this task list is filtered */
+ boolean hasFilter() {
+ return (mFilter != null);
+ }
+
+ /** Returns the list of filtered tasks */
+ ArrayList<Task> getTasks() {
+ return mFilteredTasks;
+ }
+}
+
+/**
+ * The task stack contains a list of multiple tasks.
+ */
+public class TaskStack {
+ Context mContext;
+
+ FilteredTaskList mTaskList = new FilteredTaskList();
+ TaskStackCallbacks mCb;
+
+ public TaskStack(Context context) {
+ mContext = context;
+ }
+
+ /** Sets the callbacks for this task stack */
+ public void setCallbacks(TaskStackCallbacks cb) {
+ mCb = cb;
+ }
+
+ /** Adds a new task */
+ public void addTask(Task t) {
+ mTaskList.add(t);
+ if (mCb != null) {
+ mCb.onStackTaskAdded(this, t);
+ }
+ }
+
+ /** Removes a task */
+ public void removeTask(Task t) {
+ if (mTaskList.contains(t)) {
+ mTaskList.remove(t);
+ if (mCb != null) {
+ mCb.onStackTaskRemoved(this, t);
+ }
+ }
+ }
+
+ /** Sets a few tasks in one go */
+ public void setTasks(List<Task> tasks) {
+ int taskCount = mTaskList.getTasks().size();
+ for (int i = 0; i < taskCount; i++) {
+ Task t = mTaskList.getTasks().get(i);
+ if (mCb != null) {
+ mCb.onStackTaskRemoved(this, t);
+ }
+ }
+ mTaskList.set(tasks);
+ for (Task t : tasks) {
+ if (mCb != null) {
+ mCb.onStackTaskAdded(this, t);
+ }
+ }
+ }
+
+ /** Gets the tasks */
+ public ArrayList<Task> getTasks() {
+ return mTaskList.getTasks();
+ }
+
+ /** Gets the number of tasks */
+ public int getTaskCount() {
+ return mTaskList.size();
+ }
+
+ /** Returns the index of this task in this current task stack */
+ public int indexOfTask(Task t) {
+ return mTaskList.indexOf(t);
+ }
+
+ /** Tests whether a task is in this current task stack */
+ public boolean containsTask(Task t) {
+ return mTaskList.contains(t);
+ }
+
+ /** Filters the stack into tasks similar to the one specified */
+ public void filterTasks(Task t) {
+ // Set the task list filter
+ // XXX: This is a dummy filter that currently just accepts every other task.
+ mTaskList.setFilter(new TaskFilter() {
+ @Override
+ public boolean acceptTask(Task t, int i) {
+ if (i % 2 == 0) {
+ return true;
+ }
+ return false;
+ }
+ });
+ if (mCb != null) {
+ mCb.onStackFiltered(this);
+ }
+ }
+
+ /** Unfilters the current stack */
+ public void unfilterTasks() {
+ // Unset the filter, then update the virtual scroll
+ mTaskList.removeFilter();
+ if (mCb != null) {
+ mCb.onStackUnfiltered(this);
+ }
+ }
+
+ /** Returns whether tasks are currently filtered */
+ public boolean hasFilteredTasks() {
+ return mTaskList.hasFilter();
+ }
+
+ @Override
+ public String toString() {
+ String str = "Tasks:\n";
+ for (Task t : mTaskList.getTasks()) {
+ str += " " + t.toString() + "\n";
+ }
+ return str;
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStackCallbacks.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStackCallbacks.java
new file mode 100644
index 0000000..4bec655
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStackCallbacks.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents.model;
+
+/* Task stack callbacks */
+public interface TaskStackCallbacks {
+ /* Notifies when a task has been added to the stack */
+ public void onStackTaskAdded(TaskStack stack, Task t);
+ /* Notifies when a task has been removed from the stack */
+ public void onStackTaskRemoved(TaskStack stack, Task t);
+ /** Notifies when the stack was filtered */
+ public void onStackFiltered(TaskStack stack);
+ /** Notifies when the stack was un-filtered */
+ public void onStackUnfiltered(TaskStack stack);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
new file mode 100644
index 0000000..c92041c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -0,0 +1,228 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.UserHandle;
+import android.view.View;
+import android.widget.FrameLayout;
+import com.android.systemui.recents.Console;
+import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.model.SpaceNode;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+
+import java.util.ArrayList;
+
+
+/**
+ * This view is the the top level layout that contains TaskStacks (which are laid out according
+ * to their SpaceNode bounds.
+ */
+public class RecentsView extends FrameLayout implements TaskStackViewCallbacks {
+ // The space partitioning root of this container
+ SpaceNode mBSP;
+
+ public RecentsView(Context context) {
+ super(context);
+ setWillNotDraw(false);
+ }
+
+ /** Set/get the bsp root node */
+ public void setBSP(SpaceNode n) {
+ mBSP = n;
+
+ // XXX: We shouldn't be recereating new stacks every time, but for now, that is OK
+ // Add all the stacks for this partition
+ removeAllViews();
+ ArrayList<TaskStack> stacks = mBSP.getStacks();
+ for (TaskStack stack : stacks) {
+ TaskStackView stackView = new TaskStackView(getContext(), stack);
+ stackView.setCallbacks(this);
+ addView(stackView);
+ }
+ }
+
+ /** Launches the first task from the first stack if possible */
+ public boolean launchFirstTask() {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ TaskStackView stackView = (TaskStackView) getChildAt(i);
+ TaskStack stack = stackView.mStack;
+ ArrayList<Task> tasks = stack.getTasks();
+ if (!tasks.isEmpty()) {
+ Task task = tasks.get(tasks.size() - 1);
+ TaskView tv = null;
+ if (stackView.getChildCount() > 0) {
+ TaskView stv = (TaskView) stackView.getChildAt(stackView.getChildCount() - 1);
+ if (stv.getTask() == task) {
+ tv = stv;
+ }
+ }
+ onTaskLaunched(stackView, tv, stack, task);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[RecentsView|measure]", "width: " + width + " height: " + height, Console.AnsiGreen);
+
+ // We measure our stack views sans the status bar. It will handle the nav bar itself.
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ int childHeight = height - config.systemInsets.top;
+
+ // Measure each child
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ child.measure(widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(childHeight, heightMode));
+ }
+ }
+
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[RecentsView|layout]", new Rect(left, top, right, bottom) + " changed: " + changed, Console.AnsiGreen);
+ // We offset our stack views by the status bar height. It will handle the nav bar itself.
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ top += config.systemInsets.top;
+
+ // Layout each child
+ // XXX: Based on the space node for that task view
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ final int width = child.getMeasuredWidth();
+ final int height = child.getMeasuredHeight();
+ child.layout(left, top, left + width, top + height);
+ }
+ }
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ Console.log(Constants.DebugFlags.UI.Draw, "[RecentsView|dispatchDraw]", "", Console.AnsiPurple);
+ super.dispatchDraw(canvas);
+ }
+
+ @Override
+ protected boolean fitSystemWindows(Rect insets) {
+ Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen);
+
+ // Update the configuration with the latest system insets and trigger a relayout
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ config.updateSystemInsets(insets);
+ requestLayout();
+
+ return true;
+ }
+
+ /** Unfilters any filtered stacks */
+ public boolean unfilterFilteredStacks() {
+ if (mBSP != null) {
+ // Check if there are any filtered stacks and unfilter them before we back out of Recents
+ boolean stacksUnfiltered = false;
+ ArrayList<TaskStack> stacks = mBSP.getStacks();
+ for (TaskStack stack : stacks) {
+ if (stack.hasFilteredTasks()) {
+ stack.unfilterTasks();
+ stacksUnfiltered = true;
+ }
+ }
+ return stacksUnfiltered;
+ }
+ return false;
+ }
+
+ /**** View.OnClickListener Implementation ****/
+
+ @Override
+ public void onTaskLaunched(final TaskStackView stackView, final TaskView tv,
+ final TaskStack stack, final Task task) {
+ final Runnable launchRunnable = new Runnable() {
+ @Override
+ public void run() {
+ TaskViewTransform transform;
+ View sourceView = tv;
+ int offsetX = 0;
+ int offsetY = 0;
+ if (tv == null) {
+ // Launch the activity
+ sourceView = stackView;
+ transform = stackView.getStackTransform(stack.indexOfTask(task));
+ offsetX = transform.rect.left;
+ offsetY = transform.rect.top;
+ } else {
+ transform = stackView.getStackTransform(stack.indexOfTask(task));
+ }
+
+ // Compute the thumbnail to scale up from
+ ActivityOptions opts = null;
+ int thumbnailWidth = transform.rect.width();
+ int thumbnailHeight = transform.rect.height();
+ if (task.thumbnail != null && thumbnailWidth > 0 && thumbnailHeight > 0 &&
+ task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) {
+ // Resize the thumbnail to the size of the view that we are animating from
+ Bitmap b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight,
+ Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(b);
+ c.drawBitmap(task.thumbnail,
+ new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()),
+ new Rect(0, 0, thumbnailWidth, thumbnailHeight), null);
+ c.setBitmap(null);
+ opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView,
+ b, offsetX, offsetY);
+ }
+
+ // Launch the activity with the desired animation
+ Intent i = new Intent(task.intent);
+ i.setFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
+ | Intent.FLAG_ACTIVITY_TASK_ON_HOME
+ | Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (opts != null) {
+ getContext().startActivityAsUser(i, opts.toBundle(), UserHandle.CURRENT);
+ } else {
+ getContext().startActivityAsUser(i, UserHandle.CURRENT);
+ }
+ }
+ };
+
+ // Launch the app right away if there is no task view, otherwise, animate the icon out first
+ if (tv == null || !Constants.Values.TaskView.AnimateFrontTaskIconOnLeavingRecents) {
+ launchRunnable.run();
+ } else {
+ tv.animateOnLeavingRecents(launchRunnable);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
new file mode 100644
index 0000000..fe661bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
@@ -0,0 +1,389 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.animation.LinearInterpolator;
+
+/**
+ * This class facilitates swipe to dismiss. It defines an interface to be implemented by the
+ * by the class hosting the views that need to swiped, and, using this interface, handles touch
+ * events and translates / fades / animates the view as it is dismissed.
+ */
+public class SwipeHelper {
+ static final String TAG = "SwipeHelper";
+ private static final boolean SLOW_ANIMATIONS = false; // DEBUG;
+ private static final boolean CONSTRAIN_SWIPE = true;
+ private static final boolean FADE_OUT_DURING_SWIPE = true;
+ private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
+
+ public static final int X = 0;
+ public static final int Y = 1;
+
+ private static LinearInterpolator sLinearInterpolator = new LinearInterpolator();
+
+ private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
+ private int DEFAULT_ESCAPE_ANIMATION_DURATION = 75; // ms
+ private int MAX_ESCAPE_ANIMATION_DURATION = 150; // ms
+ private int MAX_DISMISS_VELOCITY = 2000; // dp/sec
+ private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
+
+ public static float ALPHA_FADE_START = 0.15f; // fraction of thumbnail width
+ // where fade starts
+ static final float ALPHA_FADE_END = 0.65f; // fraction of thumbnail width
+ // beyond which alpha->0
+ private float mMinAlpha = 0f;
+
+ private float mPagingTouchSlop;
+ Callback mCallback;
+ private int mSwipeDirection;
+ private VelocityTracker mVelocityTracker;
+
+ private float mInitialTouchPos;
+ private boolean mDragging;
+
+ private View mCurrView;
+ private boolean mCanCurrViewBeDimissed;
+ private float mDensityScale;
+
+ public boolean mAllowSwipeTowardsStart = true;
+ public boolean mAllowSwipeTowardsEnd = true;
+ private boolean mRtl;
+
+ public SwipeHelper(int swipeDirection, Callback callback, float densityScale,
+ float pagingTouchSlop) {
+ mCallback = callback;
+ mSwipeDirection = swipeDirection;
+ mVelocityTracker = VelocityTracker.obtain();
+ mDensityScale = densityScale;
+ mPagingTouchSlop = pagingTouchSlop;
+ }
+
+ public void setDensityScale(float densityScale) {
+ mDensityScale = densityScale;
+ }
+
+ public void setPagingTouchSlop(float pagingTouchSlop) {
+ mPagingTouchSlop = pagingTouchSlop;
+ }
+
+ public void cancelOngoingDrag() {
+ if (mDragging) {
+ if (mCurrView != null) {
+ mCallback.onDragCancelled(mCurrView);
+ setTranslation(mCurrView, 0);
+ mCallback.onSnapBackCompleted(mCurrView);
+ mCurrView = null;
+ }
+ mDragging = false;
+ }
+ }
+
+ public void resetTranslation(View v) {
+ setTranslation(v, 0);
+ }
+
+ private float getPos(MotionEvent ev) {
+ return mSwipeDirection == X ? ev.getX() : ev.getY();
+ }
+
+ private float getTranslation(View v) {
+ return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
+ }
+
+ private float getVelocity(VelocityTracker vt) {
+ return mSwipeDirection == X ? vt.getXVelocity() :
+ vt.getYVelocity();
+ }
+
+ private ObjectAnimator createTranslationAnimation(View v, float newPos) {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(v,
+ mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);
+ return anim;
+ }
+
+ private float getPerpendicularVelocity(VelocityTracker vt) {
+ return mSwipeDirection == X ? vt.getYVelocity() :
+ vt.getXVelocity();
+ }
+
+ private void setTranslation(View v, float translate) {
+ if (mSwipeDirection == X) {
+ v.setTranslationX(translate);
+ } else {
+ v.setTranslationY(translate);
+ }
+ }
+
+ private float getSize(View v) {
+ final DisplayMetrics dm = v.getContext().getResources().getDisplayMetrics();
+ return mSwipeDirection == X ? dm.widthPixels : dm.heightPixels;
+ }
+
+ public void setMinAlpha(float minAlpha) {
+ mMinAlpha = minAlpha;
+ }
+
+ float getAlphaForOffset(View view) {
+ float viewSize = getSize(view);
+ final float fadeSize = ALPHA_FADE_END * viewSize;
+ float result = 1.0f;
+ float pos = getTranslation(view);
+ if (pos >= viewSize * ALPHA_FADE_START) {
+ result = 1.0f - (pos - viewSize * ALPHA_FADE_START) / fadeSize;
+ } else if (pos < viewSize * (1.0f - ALPHA_FADE_START)) {
+ result = 1.0f + (viewSize * ALPHA_FADE_START + pos) / fadeSize;
+ }
+ result = Math.min(result, 1.0f);
+ result = Math.max(result, 0f);
+ return Math.max(mMinAlpha, result);
+ }
+
+ /**
+ * Determines whether the given view has RTL layout.
+ */
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ public static boolean isLayoutRtl(View view) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return View.LAYOUT_DIRECTION_RTL == view.getLayoutDirection();
+ } else {
+ return false;
+ }
+ }
+
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ final int action = ev.getAction();
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mDragging = false;
+ mCurrView = mCallback.getChildAtPosition(ev);
+ mVelocityTracker.clear();
+ if (mCurrView != null) {
+ mRtl = isLayoutRtl(mCurrView);
+ mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
+ mVelocityTracker.addMovement(ev);
+ mInitialTouchPos = getPos(ev);
+ } else {
+ mCanCurrViewBeDimissed = false;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mCurrView != null) {
+ mVelocityTracker.addMovement(ev);
+ float pos = getPos(ev);
+ float delta = pos - mInitialTouchPos;
+ if (Math.abs(delta) > mPagingTouchSlop) {
+ mCallback.onBeginDrag(mCurrView);
+ mDragging = true;
+ mInitialTouchPos = getPos(ev) - getTranslation(mCurrView);
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mDragging = false;
+ mCurrView = null;
+ break;
+ }
+ return mDragging;
+ }
+
+ /**
+ * @param view The view to be dismissed
+ * @param velocity The desired pixels/second speed at which the view should move
+ */
+ private void dismissChild(final View view, float velocity) {
+ final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
+ float newPos;
+ if (velocity < 0
+ || (velocity == 0 && getTranslation(view) < 0)
+ // if we use the Menu to dismiss an item in landscape, animate up
+ || (velocity == 0 && getTranslation(view) == 0 && mSwipeDirection == Y)) {
+ newPos = -getSize(view);
+ } else {
+ newPos = getSize(view);
+ }
+ int duration = MAX_ESCAPE_ANIMATION_DURATION;
+ if (velocity != 0) {
+ duration = Math.min(duration,
+ (int) (Math.abs(newPos - getTranslation(view)) *
+ 1000f / Math.abs(velocity)));
+ } else {
+ duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
+ }
+
+ ValueAnimator anim = createTranslationAnimation(view, newPos);
+ anim.setInterpolator(sLinearInterpolator);
+ anim.setDuration(duration);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCallback.onChildDismissed(view);
+ if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
+ view.setAlpha(1.f);
+ }
+ }
+ });
+ anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
+ view.setAlpha(getAlphaForOffset(view));
+ }
+ }
+ });
+ anim.start();
+ }
+
+ private void snapChild(final View view, float velocity) {
+ final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
+ ValueAnimator anim = createTranslationAnimation(view, 0);
+ int duration = SNAP_ANIM_LEN;
+ anim.setDuration(duration);
+ anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
+ view.setAlpha(getAlphaForOffset(view));
+ }
+ }
+ });
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
+ view.setAlpha(1.0f);
+ }
+ mCallback.onSnapBackCompleted(view);
+ }
+ });
+ anim.start();
+ }
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (!mDragging) {
+ if (!onInterceptTouchEvent(ev)) {
+ return mCanCurrViewBeDimissed;
+ }
+ }
+
+ mVelocityTracker.addMovement(ev);
+ final int action = ev.getAction();
+ switch (action) {
+ case MotionEvent.ACTION_OUTSIDE:
+ case MotionEvent.ACTION_MOVE:
+ if (mCurrView != null) {
+ float delta = getPos(ev) - mInitialTouchPos;
+ setSwipeAmount(delta);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (mCurrView != null) {
+ endSwipe(mVelocityTracker);
+ }
+ break;
+ }
+ return true;
+ }
+
+ private void setSwipeAmount(float amount) {
+ // don't let items that can't be dismissed be dragged more than
+ // maxScrollDistance
+ if (CONSTRAIN_SWIPE
+ && (!isValidSwipeDirection(amount) || !mCallback.canChildBeDismissed(mCurrView))) {
+ float size = getSize(mCurrView);
+ float maxScrollDistance = 0.15f * size;
+ if (Math.abs(amount) >= size) {
+ amount = amount > 0 ? maxScrollDistance : -maxScrollDistance;
+ } else {
+ amount = maxScrollDistance * (float) Math.sin((amount/size)*(Math.PI/2));
+ }
+ }
+ setTranslation(mCurrView, amount);
+ if (FADE_OUT_DURING_SWIPE && mCanCurrViewBeDimissed) {
+ float alpha = getAlphaForOffset(mCurrView);
+ mCurrView.setAlpha(alpha);
+ }
+ }
+
+ private boolean isValidSwipeDirection(float amount) {
+ if (mSwipeDirection == X) {
+ if (mRtl) {
+ return (amount <= 0) ? mAllowSwipeTowardsEnd : mAllowSwipeTowardsStart;
+ } else {
+ return (amount <= 0) ? mAllowSwipeTowardsStart : mAllowSwipeTowardsEnd;
+ }
+ }
+
+ // Vertical swipes are always valid.
+ return true;
+ }
+
+ private void endSwipe(VelocityTracker velocityTracker) {
+ float maxVelocity = MAX_DISMISS_VELOCITY * mDensityScale;
+ velocityTracker.computeCurrentVelocity(1000 /* px/sec */, maxVelocity);
+ float velocity = getVelocity(velocityTracker);
+ float perpendicularVelocity = getPerpendicularVelocity(velocityTracker);
+ float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale;
+ float translation = getTranslation(mCurrView);
+ // Decide whether to dismiss the current view
+ boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH &&
+ Math.abs(translation) > 0.6 * getSize(mCurrView);
+ boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) &&
+ (Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
+ (velocity > 0) == (translation > 0);
+
+ boolean dismissChild = mCallback.canChildBeDismissed(mCurrView)
+ && isValidSwipeDirection(translation)
+ && (childSwipedFastEnough || childSwipedFarEnough);
+
+ if (dismissChild) {
+ // flingadingy
+ dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f);
+ } else {
+ // snappity
+ mCallback.onDragCancelled(mCurrView);
+ snapChild(mCurrView, velocity);
+ }
+ }
+
+ public interface Callback {
+ View getChildAtPosition(MotionEvent ev);
+
+ boolean canChildBeDismissed(View v);
+
+ void onBeginDrag(View v);
+
+ void onChildDismissed(View v);
+
+ void onSnapBackCompleted(View v);
+
+ void onDragCancelled(View v);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
new file mode 100644
index 0000000..9dd6c0b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -0,0 +1,1075 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+import android.widget.FrameLayout;
+import android.widget.OverScroller;
+import android.widget.Toast;
+import com.android.systemui.recents.Console;
+import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.RecentsTaskLoader;
+import com.android.systemui.recents.Utilities;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.model.TaskStackCallbacks;
+
+import java.util.ArrayList;
+
+/** The TaskView callbacks */
+interface TaskStackViewCallbacks {
+ public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
+}
+
+/* The visual representation of a task stack view */
+public class TaskStackView extends FrameLayout implements TaskStackCallbacks, TaskViewCallbacks,
+ ViewPoolConsumer<TaskView, Task>, View.OnClickListener {
+ TaskStack mStack;
+ TaskStackViewTouchHandler mTouchHandler;
+ TaskStackViewCallbacks mCb;
+ ViewPool<TaskView, Task> mViewPool;
+
+ // The various rects that define the stack view
+ Rect mRect = new Rect();
+ Rect mStackRect = new Rect();
+ Rect mStackRectSansPeek = new Rect();
+ Rect mTaskRect = new Rect();
+
+ // The virtual stack scroll that we use for the card layout
+ int mStackScroll;
+ int mMinScroll;
+ int mMaxScroll;
+ OverScroller mScroller;
+ ObjectAnimator mScrollAnimator;
+
+ // Optimizations
+ int mHwLayersRefCount;
+ int mStackViewsAnimationDuration;
+ boolean mStackViewsDirty = true;
+ boolean mAwaitingFirstLayout = true;
+
+ public TaskStackView(Context context, TaskStack stack) {
+ super(context);
+ mStack = stack;
+ mStack.setCallbacks(this);
+ mScroller = new OverScroller(context);
+ mTouchHandler = new TaskStackViewTouchHandler(context, this);
+ mViewPool = new ViewPool<TaskView, Task>(context, this);
+ }
+
+ /** Sets the callbacks */
+ void setCallbacks(TaskStackViewCallbacks cb) {
+ mCb = cb;
+ }
+
+ /** Requests that the views be synchronized with the model */
+ void requestSynchronizeStackViewsWithModel() {
+ requestSynchronizeStackViewsWithModel(0);
+ }
+ void requestSynchronizeStackViewsWithModel(int duration) {
+ Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel,
+ "[TaskStackView|requestSynchronize]", "", Console.AnsiYellow);
+ if (!mStackViewsDirty) {
+ invalidate();
+ }
+ if (mAwaitingFirstLayout) {
+ // Skip the animation if we are awaiting first layout
+ mStackViewsAnimationDuration = 0;
+ } else {
+ mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
+ }
+ mStackViewsDirty = true;
+ }
+
+ // XXX: Optimization: Use a mapping of Task -> View
+ private TaskView getChildViewForTask(Task t) {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ TaskView tv = (TaskView) getChildAt(i);
+ if (tv.getTask() == t) {
+ return tv;
+ }
+ }
+ return null;
+ }
+
+ /** Update/get the transform */
+ public TaskViewTransform getStackTransform(int indexInStack) {
+ TaskViewTransform transform = new TaskViewTransform();
+
+ // Map the items to an continuous position relative to the current scroll
+ int numPeekCards = Constants.Values.TaskStackView.StackPeekNumCards;
+ float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
+ float peekHeight = Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
+ float t = ((indexInStack * overlapHeight) - getStackScroll()) / overlapHeight;
+ float boundedT = Math.max(t, -(numPeekCards + 1));
+
+ // Set the scale relative to its position
+ float minScale = Constants.Values.TaskStackView.StackPeekMinScale;
+ float scaleRange = 1f - minScale;
+ float scaleInc = scaleRange / numPeekCards;
+ float scale = Math.max(minScale, Math.min(1f, 1f + (boundedT * scaleInc)));
+ float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2;
+ transform.scale = scale;
+
+ // Set the translation
+ if (boundedT < 0f) {
+ transform.translationY = (int) ((Math.max(-numPeekCards, boundedT) /
+ numPeekCards) * peekHeight - scaleYOffset);
+ } else {
+ transform.translationY = (int) (boundedT * overlapHeight - scaleYOffset);
+ }
+
+ // Update the rect and visibility
+ transform.rect.set(mTaskRect);
+ if (t < -(numPeekCards + 1)) {
+ transform.visible = false;
+ } else {
+ transform.rect.offset(0, transform.translationY);
+ Utilities.scaleRectAboutCenter(transform.rect, scale);
+ transform.visible = Rect.intersects(mRect, transform.rect);
+ }
+ transform.t = t;
+ return transform;
+ }
+
+ /** Synchronizes the views with the model */
+ void synchronizeStackViewsWithModel() {
+ Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel,
+ "[TaskStackView|synchronizeViewsWithModel]",
+ "mStackViewsDirty: " + mStackViewsDirty, Console.AnsiYellow);
+ if (mStackViewsDirty) {
+
+ // XXX: Optimization: Use binary search to find the visible range
+ // XXX: Optimize to not call getStackTransform() so many times
+ // XXX: Consider using TaskViewTransform pool to prevent allocations
+ // XXX: Iterate children views, update transforms and remove all that are not visible
+ // For all remaining tasks, update transforms and if visible add the view
+
+ // Update the visible state of all the tasks
+ ArrayList<Task> tasks = mStack.getTasks();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = tasks.get(i);
+ TaskViewTransform transform = getStackTransform(i);
+ TaskView tv = getChildViewForTask(task);
+
+ if (transform.visible) {
+ if (tv == null) {
+ tv = mViewPool.pickUpViewFromPool(task, task);
+ // When we are picking up a new view from the view pool, prepare it for any
+ // following animation by putting it in a reasonable place
+ if (mStackViewsAnimationDuration > 0 && i != 0) {
+ // XXX: We have to animate when filtering, etc. Maybe we should have a
+ // runnable that ensures that tasks are animated in a special way
+ // when they are entering the scene?
+ int fromIndex = (transform.t < 0) ? (i - 1) : (i + 1);
+ tv.updateViewPropertiesFromTask(null, getStackTransform(fromIndex), 0);
+ }
+ }
+ } else {
+ if (tv != null) {
+ mViewPool.returnViewToPool(tv);
+ }
+ }
+ }
+
+ // Update all the current view children
+ // NOTE: We have to iterate in reverse where because we are removing views directly
+ int childCount = getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ TaskView tv = (TaskView) getChildAt(i);
+ Task task = tv.getTask();
+ TaskViewTransform transform = getStackTransform(mStack.indexOfTask(task));
+ if (!transform.visible) {
+ mViewPool.returnViewToPool(tv);
+ } else {
+ tv.updateViewPropertiesFromTask(null, transform, mStackViewsAnimationDuration);
+ }
+ }
+
+ Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel,
+ " [TaskStackView|viewChildren]", "" + getChildCount());
+
+ mStackViewsAnimationDuration = 0;
+ mStackViewsDirty = false;
+ }
+ }
+
+ /** Sets the current stack scroll */
+ public void setStackScroll(int value) {
+ mStackScroll = value;
+ requestSynchronizeStackViewsWithModel();
+ }
+
+ /** Gets the current stack scroll */
+ public int getStackScroll() {
+ return mStackScroll;
+ }
+
+ /** Animates the stack scroll into bounds */
+ ObjectAnimator animateBoundScroll(int duration) {
+ int curScroll = getStackScroll();
+ int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
+ if (newScroll != curScroll) {
+ // Enable hw layers on the stack
+ addHwLayersRefCount();
+
+ // Abort any current animations
+ mScroller.abortAnimation();
+ if (mScrollAnimator != null) {
+ mScrollAnimator.cancel();
+ mScrollAnimator.removeAllListeners();
+ }
+
+ // Start a new scroll animation
+ mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
+ mScrollAnimator.setDuration(duration);
+ mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ setStackScroll((Integer) animation.getAnimatedValue());
+ }
+ });
+ mScrollAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Disable hw layers on the stack
+ decHwLayersRefCount();
+ }
+ });
+ mScrollAnimator.start();
+ }
+ return mScrollAnimator;
+ }
+
+ /** Aborts any current stack scrolls */
+ void abortBoundScrollAnimation() {
+ if (mScrollAnimator != null) {
+ mScrollAnimator.cancel();
+ }
+ }
+
+ /** Bounds the current scroll if necessary */
+ public boolean boundScroll() {
+ int curScroll = getStackScroll();
+ int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
+ if (newScroll != curScroll) {
+ setStackScroll(newScroll);
+ return true;
+ }
+ return false;
+ }
+
+ /** Returns whether the current scroll is out of bounds */
+ boolean isScrollOutOfBounds() {
+ return (getStackScroll() < 0) || (getStackScroll() > mMaxScroll);
+ }
+
+ /** Updates the min and max virtual scroll bounds */
+ void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
+ // Compute the min and max scroll values
+ int numTasks = Math.max(1, mStack.getTaskCount());
+ int taskHeight = mTaskRect.height();
+ int stackHeight = mStackRectSansPeek.height();
+ int maxScrollHeight = taskHeight + (int) ((numTasks - 1) *
+ Constants.Values.TaskStackView.StackOverlapPct * taskHeight);
+ mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight;
+ mMaxScroll = maxScrollHeight - stackHeight;
+
+ // Debug logging
+ if (Constants.DebugFlags.UI.MeasureAndLayout) {
+ Console.log(" [TaskStack|minScroll] " + mMinScroll);
+ Console.log(" [TaskStack|maxScroll] " + mMaxScroll);
+ }
+
+ if (boundScrollToNewMinMax) {
+ boundScroll();
+ }
+ }
+
+ /** Enables the hw layers and increments the hw layer requirement ref count */
+ void addHwLayersRefCount() {
+ Console.log(Constants.DebugFlags.UI.HwLayers,
+ "[TaskStackView|addHwLayersRefCount] refCount: " +
+ mHwLayersRefCount + "->" + (mHwLayersRefCount + 1));
+ if (mHwLayersRefCount == 0) {
+ // Enable hw layers on each of the children
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ TaskView tv = (TaskView) getChildAt(i);
+ tv.enableHwLayers();
+ }
+ }
+ mHwLayersRefCount++;
+ }
+
+ /** Decrements the hw layer requirement ref count and disables the hw layers when we don't
+ need them anymore. */
+ void decHwLayersRefCount() {
+ Console.log(Constants.DebugFlags.UI.HwLayers,
+ "[TaskStackView|decHwLayersRefCount] refCount: " +
+ mHwLayersRefCount + "->" + (mHwLayersRefCount - 1));
+ mHwLayersRefCount--;
+ if (mHwLayersRefCount == 0) {
+ // Disable hw layers on each of the children
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ TaskView tv = (TaskView) getChildAt(i);
+ tv.disableHwLayers();
+ }
+ } else if (mHwLayersRefCount < 0) {
+ throw new RuntimeException("Invalid hw layers ref count");
+ }
+ }
+
+ @Override
+ public void computeScroll() {
+ if (mScroller.computeScrollOffset()) {
+ setStackScroll(mScroller.getCurrY());
+ invalidate();
+
+ // If we just finished scrolling, then disable the hw layers
+ if (mScroller.isFinished()) {
+ decHwLayersRefCount();
+ }
+ }
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ return mTouchHandler.onInterceptTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return mTouchHandler.onTouchEvent(ev);
+ }
+
+ @Override
+ public void dispatchDraw(Canvas canvas) {
+ Console.log(Constants.DebugFlags.UI.Draw, "[TaskStackView|dispatchDraw]", "",
+ Console.AnsiPurple);
+ synchronizeStackViewsWithModel();
+ super.dispatchDraw(canvas);
+ }
+
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ if (Constants.DebugFlags.App.EnableTaskStackClipping) {
+ TaskView tv = (TaskView) child;
+ TaskView nextTv = null;
+ int curIndex = indexOfChild(tv);
+ if (curIndex < (getChildCount() - 1)) {
+ // Clip against the next view (if we aren't animating its alpha)
+ nextTv = (TaskView) getChildAt(curIndex + 1);
+ if (nextTv.getAlpha() == 1f) {
+ Rect curRect = tv.getClippingRect(Utilities.tmpRect, false);
+ Rect nextRect = nextTv.getClippingRect(Utilities.tmpRect2, true);
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ // The hit rects are relative to the task view, which needs to be offset by the
+ // system bar height
+ curRect.offset(0, config.systemInsets.top);
+ nextRect.offset(0, config.systemInsets.top);
+ // Compute the clip region
+ Region clipRegion = new Region();
+ clipRegion.op(curRect, Region.Op.UNION);
+ clipRegion.op(nextRect, Region.Op.DIFFERENCE);
+ // Clip the canvas
+ int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+ canvas.clipRegion(clipRegion);
+ boolean invalidate = super.drawChild(canvas, child, drawingTime);
+ canvas.restoreToCount(saveCount);
+ return invalidate;
+ }
+ }
+ }
+ return super.drawChild(canvas, child, drawingTime);
+ }
+
+ /** Computes the stack and task rects */
+ public void computeRects(int width, int height) {
+ // Note: We let the stack view be the full height because we want the cards to go under the
+ // navigation bar if possible. However, the stack rects which we use to calculate
+ // max scroll, etc. need to take the nav bar into account
+
+ // Compute the stack rects
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ mRect.set(0, 0, width, height);
+ mStackRect.set(mRect);
+ mStackRect.bottom -= config.systemInsets.bottom;
+
+ int smallestDimension = Math.min(width, height);
+ int padding = (int) (Constants.Values.TaskStackView.StackPaddingPct * smallestDimension / 2f);
+ mStackRect.inset(padding, padding);
+ mStackRectSansPeek.set(mStackRect);
+ mStackRectSansPeek.top += Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
+
+ // Compute the task rect
+ if (RecentsConfiguration.getInstance().layoutVerticalStack) {
+ int minHeight = (int) (mStackRect.height() -
+ (Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height()));
+ int size = Math.min(minHeight, Math.min(mStackRect.width(), mStackRect.height()));
+ int centerX = mStackRect.centerX();
+ mTaskRect.set(centerX - size / 2, mStackRectSansPeek.top,
+ centerX + size / 2, mStackRectSansPeek.top + size);
+ } else {
+ int size = Math.min(mStackRect.width(), mStackRect.height());
+ int centerY = mStackRect.centerY();
+ mTaskRect.set(mStackRectSansPeek.top, centerY - size / 2,
+ mStackRectSansPeek.top + size, centerY + size / 2);
+ }
+
+ // Update the scroll bounds
+ updateMinMaxScroll(false);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[TaskStackView|measure]",
+ "width: " + width + " height: " + height +
+ " awaitingFirstLayout: " + mAwaitingFirstLayout, Console.AnsiGreen);
+
+ // Compute our stack/task rects
+ computeRects(width, height);
+
+ // Debug logging
+ if (Constants.DebugFlags.UI.MeasureAndLayout) {
+ Console.log(" [TaskStack|fullRect] " + mRect);
+ Console.log(" [TaskStack|stackRect] " + mStackRect);
+ Console.log(" [TaskStack|stackRectSansPeek] " + mStackRectSansPeek);
+ Console.log(" [TaskStack|taskRect] " + mTaskRect);
+ }
+
+ // If this is the first layout, then scroll to the front of the stack and synchronize the
+ // stack views immediately
+ if (mAwaitingFirstLayout) {
+ setStackScroll(mMaxScroll);
+ requestSynchronizeStackViewsWithModel();
+ synchronizeStackViewsWithModel();
+
+ // Animate the icon of the first task view
+ if (Constants.Values.TaskView.AnimateFrontTaskIconOnEnterRecents) {
+ TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
+ if (tv != null) {
+ tv.animateOnEnterRecents();
+ }
+ }
+ }
+
+ // Measure each of the children
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ TaskView t = (TaskView) getChildAt(i);
+ t.measure(MeasureSpec.makeMeasureSpec(mTaskRect.width(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(mTaskRect.height(), MeasureSpec.EXACTLY));
+ }
+
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[TaskStackView|layout]",
+ "" + new Rect(left, top, right, bottom), Console.AnsiGreen);
+
+ // Debug logging
+ if (Constants.DebugFlags.UI.MeasureAndLayout) {
+ Console.log(" [TaskStack|fullRect] " + mRect);
+ Console.log(" [TaskStack|stackRect] " + mStackRect);
+ Console.log(" [TaskStack|stackRectSansPeek] " + mStackRectSansPeek);
+ Console.log(" [TaskStack|taskRect] " + mTaskRect);
+ }
+
+ // Layout each of the children
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ TaskView t = (TaskView) getChildAt(i);
+ t.layout(mTaskRect.left, mStackRectSansPeek.top,
+ mTaskRect.right, mStackRectSansPeek.top + mTaskRect.height());
+ }
+
+ if (!mAwaitingFirstLayout) {
+ requestSynchronizeStackViewsWithModel();
+ } else {
+ mAwaitingFirstLayout = false;
+ }
+ }
+
+ @Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ super.onScrollChanged(l, t, oldl, oldt);
+ requestSynchronizeStackViewsWithModel();
+ }
+
+ public boolean isTransformedTouchPointInView(float x, float y, View child) {
+ return isTransformedTouchPointInView(x, y, child, null);
+ }
+
+ /**** TaskStackCallbacks Implementation ****/
+
+ @Override
+ public void onStackTaskAdded(TaskStack stack, Task t) {
+ requestSynchronizeStackViewsWithModel();
+ }
+
+ @Override
+ public void onStackTaskRemoved(TaskStack stack, Task t) {
+ // Remove the view associated with this task, we can't rely on updateTransforms
+ // to work here because the task is no longer in the list
+ int childCount = getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ TaskView tv = (TaskView) getChildAt(i);
+ if (tv.getTask() == t) {
+ mViewPool.returnViewToPool(tv);
+ break;
+ }
+ }
+
+ updateMinMaxScroll(true);
+ requestSynchronizeStackViewsWithModel(Constants.Values.TaskStackView.Animation.TaskRemovedReshuffleDuration);
+ }
+
+ @Override
+ public void onStackFiltered(TaskStack stack) {
+ requestSynchronizeStackViewsWithModel();
+ }
+
+ @Override
+ public void onStackUnfiltered(TaskStack stack) {
+ requestSynchronizeStackViewsWithModel();
+ }
+
+ /**** ViewPoolConsumer Implementation ****/
+
+ @Override
+ public TaskView createView(Context context) {
+ Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "[TaskStackView|createPoolView]");
+ return new TaskView(context);
+ }
+
+ @Override
+ public void prepareViewToEnterPool(TaskView tv) {
+ Task task = tv.getTask();
+ tv.resetViewProperties();
+ Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]",
+ tv.getTask() + " tv: " + tv);
+
+ // Report that this tasks's data is no longer being used
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ loader.unloadTaskData(task);
+ tv.unbindFromTask();
+
+ // Detach the view from the hierarchy
+ detachViewFromParent(tv);
+
+ // Disable hw layers on this view
+ tv.disableHwLayers();
+ }
+
+ @Override
+ public void prepareViewToLeavePool(TaskView tv, Task prepareData, boolean isNewView) {
+ Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]",
+ "isNewView: " + isNewView);
+
+ // Setup and attach the view to the window
+ Task task = prepareData;
+ // We try and rebind the task (this MUST be done before the task filled)
+ tv.bindToTask(task, this);
+ // Request that this tasks's data be filled
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ loader.loadTaskData(task);
+ tv.syncToTask();
+
+ // Find the index where this task should be placed in the children
+ int insertIndex = -1;
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ Task tvTask = ((TaskView) getChildAt(i)).getTask();
+ if (mStack.containsTask(task) && (mStack.indexOfTask(task) < mStack.indexOfTask(tvTask))) {
+ insertIndex = i;
+ break;
+ }
+ }
+
+ // Add/attach the view to the hierarchy
+ Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, " [TaskStackView|insertIndex]",
+ "" + insertIndex);
+ if (isNewView) {
+ addView(tv, insertIndex);
+ tv.setOnClickListener(this);
+ } else {
+ attachViewToParent(tv, insertIndex, tv.getLayoutParams());
+ }
+
+ // Enable hw layers on this view if hw layers are enabled on the stack
+ if (mHwLayersRefCount > 0) {
+ tv.enableHwLayers();
+ }
+ }
+
+ @Override
+ public boolean hasPreferredData(TaskView tv, Task preferredData) {
+ return (tv.getTask() == preferredData);
+ }
+
+ /**** TaskViewCallbacks Implementation ****/
+
+ @Override
+ public void onTaskIconClicked(TaskView tv) {
+ Console.log(Constants.DebugFlags.UI.ClickEvents, "[TaskStack|Clicked|Icon]",
+ tv.getTask() + " is currently filtered: " + mStack.hasFilteredTasks(),
+ Console.AnsiCyan);
+ if (Constants.DebugFlags.App.EnableTaskFiltering) {
+ if (mStack.hasFilteredTasks()) {
+ mStack.unfilterTasks();
+ } else {
+ mStack.filterTasks(tv.getTask());
+ }
+ } else {
+ Toast.makeText(getContext(), "Task Filtering TBD", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ /**** View.OnClickListener Implementation ****/
+
+ @Override
+ public void onClick(View v) {
+ TaskView tv = (TaskView) v;
+ Task task = tv.getTask();
+ Console.log(Constants.DebugFlags.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
+ task + " cb: " + mCb);
+
+ if (mCb != null) {
+ mCb.onTaskLaunched(this, tv, mStack, task);
+ }
+ }
+}
+
+/* Handles touch events */
+class TaskStackViewTouchHandler {
+ static int INACTIVE_POINTER_ID = -1;
+
+ TaskStackView mSv;
+ VelocityTracker mVelocityTracker;
+
+ boolean mIsScrolling;
+ boolean mIsSwiping;
+
+ int mInitialMotionX, mInitialMotionY;
+ int mLastMotionX, mLastMotionY;
+ int mActivePointerId = INACTIVE_POINTER_ID;
+ TaskView mActiveTaskView = null;
+
+ int mTotalScrollMotion;
+ int mMinimumVelocity;
+ int mMaximumVelocity;
+ // The scroll touch slop is used to calculate when we start scrolling
+ int mScrollTouchSlop;
+ // The swipe touch slop is used to calculate when we start swiping left/right, this takes
+ // precendence over the scroll touch slop in case the user makes a gesture that starts scrolling
+ // but is intended to be a swipe
+ int mSwipeTouchSlop;
+ // After a certain amount of scrolling, we should start ignoring checks for swiping
+ int mMaxScrollMotionToRejectSwipe;
+
+ public TaskStackViewTouchHandler(Context context, TaskStackView sv) {
+ ViewConfiguration configuration = ViewConfiguration.get(context);
+ mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ mScrollTouchSlop = configuration.getScaledTouchSlop();
+ mSwipeTouchSlop = 2 * mScrollTouchSlop;
+ mMaxScrollMotionToRejectSwipe = 4 * mScrollTouchSlop;
+ mSv = sv;
+ }
+
+ /** Velocity tracker helpers */
+ void initOrResetVelocityTracker() {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ } else {
+ mVelocityTracker.clear();
+ }
+ }
+ void initVelocityTrackerIfNotExists() {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ }
+ void recycleVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ /** Returns the view at the specified coordinates */
+ TaskView findViewAtPoint(int x, int y) {
+ int childCount = mSv.getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ TaskView tv = (TaskView) mSv.getChildAt(i);
+ if (tv.getVisibility() == View.VISIBLE) {
+ if (mSv.isTransformedTouchPointInView(x, y, tv)) {
+ return tv;
+ }
+ }
+ }
+ return null;
+ }
+
+ /** Touch preprocessing for handling below */
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ Console.log(Constants.DebugFlags.UI.TouchEvents,
+ "[TaskStackViewTouchHandler|interceptTouchEvent]",
+ Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
+
+ boolean hasChildren = (mSv.getChildCount() > 0);
+ if (!hasChildren) {
+ return false;
+ }
+
+ boolean wasScrolling = !mSv.mScroller.isFinished() ||
+ (mSv.mScrollAnimator != null && mSv.mScrollAnimator.isRunning());
+ int action = ev.getAction();
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN: {
+ // Save the touch down info
+ mInitialMotionX = mLastMotionX = (int) ev.getX();
+ mInitialMotionY = mLastMotionY = (int) ev.getY();
+ mActivePointerId = ev.getPointerId(0);
+ mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
+ // Stop the current scroll if it is still flinging
+ mSv.mScroller.abortAnimation();
+ mSv.abortBoundScrollAnimation();
+ // Initialize the velocity tracker
+ initOrResetVelocityTracker();
+ mVelocityTracker.addMovement(ev);
+ // Check if the scroller is finished yet
+ mIsScrolling = !mSv.mScroller.isFinished();
+ mIsSwiping = false;
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ if (mActivePointerId == INACTIVE_POINTER_ID) break;
+
+ int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+ int y = (int) ev.getY(activePointerIndex);
+ int x = (int) ev.getX(activePointerIndex);
+ if (mActiveTaskView != null &&
+ mTotalScrollMotion < mMaxScrollMotionToRejectSwipe &&
+ Math.abs(x - mInitialMotionX) > Math.abs(y - mInitialMotionY) &&
+ Math.abs(x - mInitialMotionX) > mSwipeTouchSlop) {
+ // Start swiping and stop scrolling
+ mIsScrolling = false;
+ mIsSwiping = true;
+ System.out.println("SWIPING: " + mActiveTaskView);
+ // Initialize the velocity tracker if necessary
+ initOrResetVelocityTracker();
+ mVelocityTracker.addMovement(ev);
+ // Disallow parents from intercepting touch events
+ final ViewParent parent = mSv.getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ // Enable HW layers
+ mSv.addHwLayersRefCount();
+ } else if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
+ // Save the touch move info
+ mIsScrolling = true;
+ // Initialize the velocity tracker if necessary
+ initVelocityTrackerIfNotExists();
+ mVelocityTracker.addMovement(ev);
+ // Disallow parents from intercepting touch events
+ final ViewParent parent = mSv.getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ // Enable HW layers
+ mSv.addHwLayersRefCount();
+ }
+
+ mLastMotionX = x;
+ mLastMotionY = y;
+ break;
+ }
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP: {
+ // Animate the scroll back if we've cancelled
+ mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration);
+ // Reset the drag state and the velocity tracker
+ mIsScrolling = false;
+ mIsSwiping = false;
+ mActivePointerId = INACTIVE_POINTER_ID;
+ mActiveTaskView = null;
+ mTotalScrollMotion = 0;
+ recycleVelocityTracker();
+ break;
+ }
+ }
+
+ return wasScrolling || mIsScrolling || mIsSwiping;
+ }
+
+ /** Handles touch events once we have intercepted them */
+ public boolean onTouchEvent(MotionEvent ev) {
+ Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel,
+ "[TaskStackViewTouchHandler|touchEvent]",
+ Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
+
+ // Short circuit if we have no children
+ boolean hasChildren = (mSv.getChildCount() > 0);
+ if (!hasChildren) {
+ return false;
+ }
+
+ // Update the velocity tracker
+ initVelocityTrackerIfNotExists();
+ mVelocityTracker.addMovement(ev);
+
+ int action = ev.getAction();
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN: {
+ // Save the touch down info
+ mInitialMotionX = mLastMotionX = (int) ev.getX();
+ mInitialMotionY = mLastMotionY = (int) ev.getY();
+ mActivePointerId = ev.getPointerId(0);
+ mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
+ // Stop the current scroll if it is still flinging
+ mSv.mScroller.abortAnimation();
+ mSv.abortBoundScrollAnimation();
+ // Initialize the velocity tracker
+ initOrResetVelocityTracker();
+ mVelocityTracker.addMovement(ev);
+ // XXX: Set mIsScrolling or mIsSwiping?
+ // Disallow parents from intercepting touch events
+ final ViewParent parent = mSv.getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ if (mActivePointerId == INACTIVE_POINTER_ID) break;
+
+ int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+ int x = (int) ev.getX(activePointerIndex);
+ int y = (int) ev.getY(activePointerIndex);
+ int deltaY = mLastMotionY - y;
+ int deltaX = x - mLastMotionX;
+ if (!mIsSwiping) {
+ if (mActiveTaskView != null &&
+ mTotalScrollMotion < mMaxScrollMotionToRejectSwipe &&
+ Math.abs(x - mInitialMotionX) > Math.abs(y - mInitialMotionY) &&
+ Math.abs(x - mInitialMotionX) > mSwipeTouchSlop) {
+ mIsScrolling = false;
+ mIsSwiping = true;
+ System.out.println("SWIPING: " + mActiveTaskView);
+ // Initialize the velocity tracker if necessary
+ initOrResetVelocityTracker();
+ mVelocityTracker.addMovement(ev);
+ // Disallow parents from intercepting touch events
+ final ViewParent parent = mSv.getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ // Enable HW layers
+ mSv.addHwLayersRefCount();
+ }
+ }
+ if (!mIsSwiping && !mIsScrolling) {
+ if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
+ mIsScrolling = true;
+ // Initialize the velocity tracker
+ initOrResetVelocityTracker();
+ mVelocityTracker.addMovement(ev);
+ // Disallow parents from intercepting touch events
+ final ViewParent parent = mSv.getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ // Enable HW layers
+ mSv.addHwLayersRefCount();
+ }
+ }
+ if (mIsScrolling) {
+ mSv.setStackScroll(mSv.getStackScroll() + deltaY);
+ if (mSv.isScrollOutOfBounds()) {
+ mVelocityTracker.clear();
+ }
+ } else if (mIsSwiping) {
+ mActiveTaskView.setTranslationX(mActiveTaskView.getTranslationX() + deltaX);
+ }
+ mLastMotionX = x;
+ mLastMotionY = y;
+ mTotalScrollMotion += Math.abs(deltaY);
+ break;
+ }
+ case MotionEvent.ACTION_UP: {
+ if (mIsScrolling || mIsSwiping) {
+ final TaskView activeTv = mActiveTaskView;
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+
+ if (mIsSwiping) {
+ int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
+ if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
+ // Fling to dismiss
+ int newScrollX = (int) (Math.signum(initialVelocity) *
+ activeTv.getMeasuredWidth());
+ int duration = Math.min(Constants.Values.TaskStackView.Animation.SwipeDismissDuration,
+ (int) (Math.abs(newScrollX - activeTv.getScrollX()) *
+ 1000f / Math.abs(initialVelocity)));
+ activeTv.animate()
+ .translationX(newScrollX)
+ .alpha(0f)
+ .setDuration(duration)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ Task task = activeTv.getTask();
+ Activity activity = (Activity) mSv.getContext();
+
+ // We have to disable the listener to ensure that we
+ // don't hit this again
+ activeTv.animate().setListener(null);
+
+ // Remove the task from the view
+ mSv.mStack.removeTask(task);
+
+ // Remove any stored data from the loader
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ loader.deleteTaskData(task);
+
+ // Remove the task from activity manager
+ final ActivityManager am = (ActivityManager)
+ activity.getSystemService(Context.ACTIVITY_SERVICE);
+ if (am != null) {
+ am.removeTask(activeTv.getTask().id,
+ ActivityManager.REMOVE_TASK_KILL_PROCESS);
+ }
+
+ // If there are no remaining tasks, then just close the activity
+ if (mSv.mStack.getTaskCount() == 0) {
+ activity.finish();
+ }
+
+ // Disable HW layers
+ mSv.decHwLayersRefCount();
+ }
+ })
+ .start();
+ // Enable HW layers
+ mSv.addHwLayersRefCount();
+ } else {
+ // Animate it back into place
+ // XXX: Make this animation a function of the velocity OR distance
+ int duration = Constants.Values.TaskStackView.Animation.SwipeSnapBackDuration;
+ activeTv.animate()
+ .translationX(0)
+ .setDuration(duration)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Disable HW layers
+ mSv.decHwLayersRefCount();
+ }
+ })
+ .start();
+ // Enable HW layers
+ mSv.addHwLayersRefCount();
+ }
+ } else {
+ int velocity = (int) velocityTracker.getYVelocity(mActivePointerId);
+ if ((Math.abs(velocity) > mMinimumVelocity)) {
+ Console.log(Constants.DebugFlags.UI.TouchEvents,
+ "[TaskStackViewTouchHandler|fling]",
+ "scroll: " + mSv.getStackScroll() + " velocity: " + velocity,
+ Console.AnsiGreen);
+ // Enable HW layers on the stack
+ mSv.addHwLayersRefCount();
+ // Fling scroll
+ mSv.mScroller.fling(0, mSv.getStackScroll(),
+ 0, -velocity,
+ 0, 0,
+ mSv.mMinScroll, mSv.mMaxScroll,
+ 0, 0);
+ // Invalidate to kick off computeScroll
+ mSv.invalidate();
+ } else if (mSv.isScrollOutOfBounds()) {
+ // Animate the scroll back into bounds
+ // XXX: Make this animation a function of the velocity OR distance
+ mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration);
+ }
+ }
+ }
+
+ mActivePointerId = INACTIVE_POINTER_ID;
+ mIsScrolling = false;
+ mIsSwiping = false;
+ mTotalScrollMotion = 0;
+ recycleVelocityTracker();
+ // Disable HW layers
+ mSv.decHwLayersRefCount();
+ break;
+ }
+ case MotionEvent.ACTION_CANCEL: {
+ if (mIsScrolling || mIsSwiping) {
+ if (mIsSwiping) {
+ // Animate it back into place
+ // XXX: Make this animation a function of the velocity OR distance
+ int duration = Constants.Values.TaskStackView.Animation.SwipeSnapBackDuration;
+ mActiveTaskView.animate()
+ .translationX(0)
+ .setDuration(duration)
+ .start();
+ } else {
+ // Animate the scroll back into bounds
+ // XXX: Make this animation a function of the velocity OR distance
+ mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration);
+ }
+ }
+
+ mActivePointerId = INACTIVE_POINTER_ID;
+ mIsScrolling = false;
+ mIsSwiping = false;
+ mTotalScrollMotion = 0;
+ recycleVelocityTracker();
+ // Disable HW layers
+ mSv.decHwLayersRefCount();
+ break;
+ }
+ }
+ return true;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
new file mode 100644
index 0000000..b1d0d13
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -0,0 +1,386 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.view.Gravity;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import com.android.systemui.recents.Console;
+import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskCallbacks;
+
+/** The TaskView callbacks */
+interface TaskViewCallbacks {
+ public void onTaskIconClicked(TaskView tv);
+ // public void onTaskViewReboundToTask(TaskView tv, Task t);
+}
+
+/** The task thumbnail view */
+class TaskThumbnailView extends ImageView {
+ Task mTask;
+ int mBarColor;
+
+ Path mRoundedRectClipPath = new Path();
+
+ public TaskThumbnailView(Context context) {
+ super(context);
+ setScaleType(ScaleType.FIT_XY);
+ }
+
+ /** Binds the thumbnail view to the task */
+ void rebindToTask(Task t, boolean animate) {
+ mTask = t;
+ if (t.thumbnail != null) {
+ // Update the bar color
+ if (Constants.Values.TaskView.DrawColoredTaskBars) {
+ int[] colors = {0xFFCC0C39, 0xFFE6781E, 0xFFC8CF02, 0xFF1693A7};
+ mBarColor = colors[mTask.intent.getComponent().getPackageName().length() % colors.length];
+ }
+
+ setImageBitmap(t.thumbnail);
+ if (animate) {
+ setAlpha(0f);
+ animate().alpha(1f)
+ .setDuration(Constants.Values.TaskView.Animation.TaskDataUpdatedFadeDuration)
+ .start();
+ }
+ }
+ }
+
+ /** Unbinds the thumbnail view from the task */
+ void unbindFromTask() {
+ mTask = null;
+ setImageDrawable(null);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ // Update the rounded rect clip path
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ float radius = config.pxFromDp(Constants.Values.TaskView.RoundedCornerRadiusDps);
+ mRoundedRectClipPath.reset();
+ mRoundedRectClipPath.addRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()),
+ radius, radius, Path.Direction.CW);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (Constants.Values.TaskView.UseRoundedCorners) {
+ canvas.clipPath(mRoundedRectClipPath);
+ }
+
+ super.onDraw(canvas);
+
+ if (Constants.Values.TaskView.DrawColoredTaskBars) {
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ int taskBarHeight = config.pxFromDp(Constants.Values.TaskView.TaskBarHeightDps);
+ // XXX: If we actually use this, this should be pulled out into a TextView that we
+ // inflate
+
+ // Draw the task bar
+ Rect r = new Rect();
+ Paint p = new Paint();
+ p.setAntiAlias(true);
+ p.setSubpixelText(true);
+ p.setColor(mBarColor);
+ p.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
+ canvas.drawRect(0, 0, getMeasuredWidth(), taskBarHeight, p);
+ p.setColor(0xFFffffff);
+ p.setTextSize(68);
+ p.getTextBounds("X", 0, 1, r);
+ int offset = (int) (taskBarHeight - r.height()) / 2;
+ canvas.drawText(mTask.title, offset, offset + r.height(), p);
+ }
+ }
+}
+
+/* The task icon view */
+class TaskIconView extends ImageView {
+ Task mTask;
+
+ Path mClipPath = new Path();
+ float mClipRadius;
+ Point mClipOrigin = new Point();
+ ObjectAnimator mCircularClipAnimator;
+
+ public TaskIconView(Context context) {
+ super(context);
+ mClipPath = new Path();
+ mClipRadius = 1f;
+ }
+
+ /** Binds the icon view to the task */
+ void rebindToTask(Task t, boolean animate) {
+ mTask = t;
+ if (t.icon != null) {
+ setImageDrawable(t.icon);
+ if (animate) {
+ setAlpha(0f);
+ animate().alpha(1f)
+ .setDuration(Constants.Values.TaskView.Animation.TaskDataUpdatedFadeDuration)
+ .start();
+ }
+ }
+ }
+
+ /** Unbinds the icon view from the task */
+ void unbindFromTask() {
+ mTask = null;
+ setImageDrawable(null);
+ }
+
+ /** Sets the circular clip radius on the icon */
+ public void setCircularClipRadius(float r) {
+ Console.log(Constants.DebugFlags.UI.Clipping, "[TaskView|setCircularClip]", "" + r);
+ mClipRadius = r;
+ invalidate();
+ }
+
+ /** Gets the circular clip radius on the icon */
+ public float getCircularClipRadius() {
+ return mClipRadius;
+ }
+
+ /** Animates the circular clip radius on the icon */
+ void animateCircularClip(boolean brNotTl, float newRadius, int duration, int startDelay,
+ TimeInterpolator interpolator,
+ AnimatorListenerAdapter listener) {
+ if (mCircularClipAnimator != null) {
+ mCircularClipAnimator.cancel();
+ mCircularClipAnimator.removeAllListeners();
+ }
+ if (brNotTl) {
+ mClipOrigin.set(0, 0);
+ } else {
+ mClipOrigin.set(getMeasuredWidth(), getMeasuredHeight());
+ }
+ mCircularClipAnimator = ObjectAnimator.ofFloat(this, "circularClipRadius", newRadius);
+ mCircularClipAnimator.setStartDelay(startDelay);
+ mCircularClipAnimator.setDuration(duration);
+ mCircularClipAnimator.setInterpolator(interpolator);
+ if (listener != null) {
+ mCircularClipAnimator.addListener(listener);
+ }
+ mCircularClipAnimator.start();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+ int width = getMeasuredWidth();
+ int height = getMeasuredHeight();
+ int maxSize = (int) Math.ceil(Math.sqrt(width * width + height * height));
+ mClipPath.reset();
+ mClipPath.addCircle(mClipOrigin.x, mClipOrigin.y, mClipRadius * maxSize, Path.Direction.CW);
+ canvas.clipPath(mClipPath);
+ super.onDraw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+}
+
+/* A task view */
+public class TaskView extends FrameLayout implements View.OnClickListener, TaskCallbacks {
+ Task mTask;
+ TaskThumbnailView mThumbnailView;
+ TaskIconView mIconView;
+ TaskViewCallbacks mCb;
+
+ public TaskView(Context context) {
+ super(context);
+ mThumbnailView = new TaskThumbnailView(context);
+ mIconView = new TaskIconView(context);
+ mIconView.setOnClickListener(this);
+ addView(mThumbnailView);
+ addView(mIconView);
+
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ int barHeight = config.pxFromDp(Constants.Values.TaskView.TaskBarHeightDps);
+ int iconSize = config.pxFromDp(Constants.Values.TaskView.TaskIconSizeDps);
+ int offset = barHeight - (iconSize / 2);
+
+ // XXX: Lets keep the icon in the corner for the time being
+ offset = iconSize / 4;
+
+ /*
+ ((LayoutParams) mThumbnailView.getLayoutParams()).leftMargin = barHeight / 2;
+ ((LayoutParams) mThumbnailView.getLayoutParams()).rightMargin = barHeight / 2;
+ ((LayoutParams) mThumbnailView.getLayoutParams()).bottomMargin = barHeight;
+ */
+ ((LayoutParams) mIconView.getLayoutParams()).gravity = Gravity.END;
+ ((LayoutParams) mIconView.getLayoutParams()).width = iconSize;
+ ((LayoutParams) mIconView.getLayoutParams()).height = iconSize;
+ ((LayoutParams) mIconView.getLayoutParams()).topMargin = offset;
+ ((LayoutParams) mIconView.getLayoutParams()).rightMargin = offset;
+ }
+
+ /** Set the task and callback */
+ void bindToTask(Task t, TaskViewCallbacks cb) {
+ mTask = t;
+ mTask.setCallbacks(this);
+ mCb = cb;
+ }
+
+ /** Actually synchronizes the model data into the views */
+ void syncToTask() {
+ mThumbnailView.rebindToTask(mTask, false);
+ mIconView.rebindToTask(mTask, false);
+ }
+
+ /** Unset the task and callback */
+ void unbindFromTask() {
+ mTask.setCallbacks(null);
+ mThumbnailView.unbindFromTask();
+ mIconView.unbindFromTask();
+ }
+
+ /** Gets the task */
+ Task getTask() {
+ return mTask;
+ }
+
+ /** Synchronizes this view's properties with the task's transform */
+ void updateViewPropertiesFromTask(TaskViewTransform animateFromTransform,
+ TaskViewTransform transform, int duration) {
+ if (duration > 0) {
+ if (animateFromTransform != null) {
+ setTranslationY(animateFromTransform.translationY);
+ setScaleX(animateFromTransform.scale);
+ setScaleY(animateFromTransform.scale);
+ }
+ animate().translationY(transform.translationY)
+ .scaleX(transform.scale)
+ .scaleY(transform.scale)
+ .setDuration(duration)
+ .setInterpolator(new AccelerateDecelerateInterpolator())
+ .start();
+ } else {
+ setTranslationY(transform.translationY);
+ setScaleX(transform.scale);
+ setScaleY(transform.scale);
+ }
+ }
+
+ /** Resets this view's properties */
+ void resetViewProperties() {
+ setTranslationX(0f);
+ setTranslationY(0f);
+ setScaleX(1f);
+ setScaleY(1f);
+ setAlpha(1f);
+ }
+
+ /** Animates this task view as it enters recents */
+ public void animateOnEnterRecents() {
+ mIconView.setCircularClipRadius(0f);
+ mIconView.animateCircularClip(true, 1f,
+ Constants.Values.TaskView.Animation.TaskIconCircularClipInDuration,
+ 300, new AccelerateInterpolator(), null);
+ }
+
+ /** Animates this task view as it exits recents */
+ public void animateOnLeavingRecents(final Runnable r) {
+ if (Constants.Values.TaskView.AnimateFrontTaskIconOnLeavingUseClip) {
+ mIconView.animateCircularClip(false, 0f,
+ Constants.Values.TaskView.Animation.TaskIconCircularClipOutDuration, 0,
+ new DecelerateInterpolator(),
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ r.run();
+ }
+ });
+ } else {
+ mIconView.animate()
+ .alpha(0f)
+ .setDuration(Constants.Values.TaskView.Animation.TaskIconCircularClipOutDuration)
+ .setInterpolator(new DecelerateInterpolator())
+ .setListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ r.run();
+ }
+ })
+ .start();
+ }
+ }
+
+ /** Returns the rect we want to clip (it may not be the full rect) */
+ Rect getClippingRect(Rect outRect, boolean accountForRoundedRects) {
+ getHitRect(outRect);
+ // XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster
+ outRect.right = outRect.left + mThumbnailView.getRight();
+ outRect.bottom = outRect.top + mThumbnailView.getBottom();
+ // We need to shrink the next rect by the rounded corners since those are draw on
+ // top of the current view
+ if (accountForRoundedRects) {
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ float radius = config.pxFromDp(Constants.Values.TaskView.RoundedCornerRadiusDps);
+ outRect.inset((int) radius, (int) radius);
+ }
+ return outRect;
+ }
+
+ /** Enable the hw layers on this task view */
+ void enableHwLayers() {
+ Console.log(Constants.DebugFlags.UI.HwLayers, "[TaskView|enableHwLayers]");
+ mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+
+ /** Disable the hw layers on this task view */
+ void disableHwLayers() {
+ Console.log(Constants.DebugFlags.UI.HwLayers, "[TaskView|disableHwLayers]");
+ mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+
+ @Override
+ public void onTaskDataChanged(Task task) {
+ Console.log(Constants.DebugFlags.App.EnableBackgroundTaskLoading,
+ "[TaskView|onTaskDataChanged]", task);
+
+ // Only update this task view if the changed task is the same as the task for this view
+ if (mTask == task) {
+ mThumbnailView.rebindToTask(mTask, true);
+ mIconView.rebindToTask(mTask, true);
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ mCb.onTaskIconClicked(this);
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
new file mode 100644
index 0000000..66c52a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.graphics.Rect;
+
+
+/* The transform state for a task view */
+public class TaskViewTransform {
+ public int translationY = 0;
+ public float scale = 1f;
+ public boolean visible = true;
+ public Rect rect = new Rect();
+ float t;
+
+ @Override
+ public String toString() {
+ return "TaskViewTransform y: " + translationY + " scale: " + scale +
+ " visible: " + visible + " rect: " + rect;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
new file mode 100644
index 0000000..f7d7095
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.content.Context;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+
+/* A view pool to manage more views than we can visibly handle */
+public class ViewPool<V, T> {
+ Context mContext;
+ ViewPoolConsumer<V, T> mViewCreator;
+ LinkedList<V> mPool = new LinkedList<V>();
+
+ /** Initializes the pool with a fixed predetermined pool size */
+ public ViewPool(Context context, ViewPoolConsumer<V, T> viewCreator) {
+ mContext = context;
+ mViewCreator = viewCreator;
+ }
+
+ /** Returns a view into the pool */
+ void returnViewToPool(V v) {
+ mViewCreator.prepareViewToEnterPool(v);
+ mPool.push(v);
+ }
+
+ /** Gets a view from the pool and prepares it */
+ V pickUpViewFromPool(T preferredData, T prepareData) {
+ V v = null;
+ boolean isNewView = false;
+ if (mPool.isEmpty()) {
+ v = mViewCreator.createView(mContext);
+ isNewView = true;
+ } else {
+ // Try and find a preferred view
+ Iterator<V> iter = mPool.iterator();
+ while (iter.hasNext()) {
+ V vpv = iter.next();
+ if (mViewCreator.hasPreferredData(vpv, preferredData)) {
+ v = vpv;
+ iter.remove();
+ break;
+ }
+ }
+ // Otherwise, just grab the first view
+ if (v == null) {
+ v = mPool.pop();
+ }
+ }
+ mViewCreator.prepareViewToLeavePool(v, prepareData, isNewView);
+ return v;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPoolConsumer.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPoolConsumer.java
new file mode 100644
index 0000000..50f45bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPoolConsumer.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.content.Context;
+
+
+/* An interface to the consumer of a view pool */
+public interface ViewPoolConsumer<V, T> {
+ public V createView(Context context);
+ public void prepareViewToEnterPool(V v);
+ public void prepareViewToLeavePool(V v, T prepareData, boolean isNewView);
+ public boolean hasPreferredData(V v, T preferredData);
+}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 9ecb917..15a68ef 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -3988,7 +3988,7 @@
telephonyService.silenceRinger();
} else if ((mIncallPowerBehavior
& Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
- && telephonyService.isOffhook()) {
+ && telephonyService.isOffhook() && isScreenOn) {
// Otherwise, if "Power button ends call" is enabled,
// the Power button will hang up any current active call.
hungUp = telephonyService.endCall();
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 0b3eb12..6827b3f 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -59,6 +59,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -78,6 +79,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
@@ -441,6 +443,10 @@
hideInputMethodMenu();
// No need to updateActive
return;
+ } else if (Intent.ACTION_USER_ADDED.equals(action)
+ || Intent.ACTION_USER_REMOVED.equals(action)) {
+ updateRelatedUserIds();
+ return;
} else {
Slog.w(TAG, "Unexpected intent " + intent);
}
@@ -642,6 +648,8 @@
broadcastFilter.addAction(Intent.ACTION_SCREEN_ON);
broadcastFilter.addAction(Intent.ACTION_SCREEN_OFF);
broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
+ broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
mNotificationShown = false;
@@ -675,6 +683,7 @@
// mSettings should be created before buildInputMethodListLocked
mSettings = new InputMethodSettings(
mRes, context.getContentResolver(), mMethodMap, mMethodList, userId);
+ updateRelatedUserIds();
mFileManager = new InputMethodFileManager(mMethodMap, userId);
mSwitchingController = new InputMethodSubtypeSwitchingController(mSettings);
mSwitchingController.resetCircularListLocked(context);
@@ -790,6 +799,7 @@
private void switchUserLocked(int newUserId) {
mSettings.setCurrentUserId(newUserId);
+ updateRelatedUserIds();
// InputMethodFileManager should be reset when the user is changed
mFileManager = new InputMethodFileManager(mMethodMap, newUserId);
final String defaultImiId = mSettings.getSelectedInputMethod();
@@ -810,6 +820,16 @@
}
}
+ void updateRelatedUserIds() {
+ List<UserInfo> relatedUsers =
+ UserManager.get(mContext).getRelatedUsers(mSettings.getCurrentUserId());
+ int[] relatedUserIds = new int[relatedUsers.size()]; // relatedUsers will not be null
+ for (int i = 0; i < relatedUserIds.length; i++) {
+ relatedUserIds[i] = relatedUsers.get(i).id;
+ }
+ mSettings.setRelatedUserIds(relatedUserIds);
+ }
+
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -905,7 +925,7 @@
+ mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()
+ InputMethodUtils.getApiCallStack());
}
- if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) {
+ if (uid == Process.SYSTEM_UID || mSettings.isRelatedToOrCurrentUser(userId)) {
return true;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d910861..128f636 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1015,6 +1015,7 @@
final ActivityThread mSystemThread;
int mCurrentUserId = 0;
+ int[] mRelatedUserIds = new int[0]; // Accessed by ActivityStack
private UserManagerService mUserManager;
private final class AppDeathRecipient implements IBinder.DeathRecipient {
@@ -7244,6 +7245,24 @@
}
@Override
+ public boolean isInHomeStack(int taskId) {
+ enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+ "getStackInfo()");
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ TaskRecord tr = recentTaskForIdLocked(taskId);
+ if (tr != null) {
+ return tr.stack.isHomeStack();
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return false;
+ }
+
+ @Override
public int getTaskForActivity(IBinder token, boolean onlyRoot) {
synchronized(this) {
return ActivityRecord.getTaskForActivityLocked(token, onlyRoot);
@@ -16127,6 +16146,20 @@
return startUser(userId, /* foreground */ false);
}
+ /**
+ * Refreshes the list of users related to the current user when either a
+ * user switch happens or when a new related user is started in the
+ * background.
+ */
+ private void updateRelatedUserIdsLocked() {
+ final List<UserInfo> relatedUsers = getUserManagerLocked().getRelatedUsers(mCurrentUserId);
+ int[] relatedUserIds = new int[relatedUsers.size()]; // relatedUsers will not be null
+ for (int i = 0; i < relatedUserIds.length; i++) {
+ relatedUserIds[i] = relatedUsers.get(i).id;
+ }
+ mRelatedUserIds = relatedUserIds;
+ }
+
@Override
public boolean switchUser(final int userId) {
return startUser(userId, /* foregound */ true);
@@ -16180,12 +16213,15 @@
if (foreground) {
mCurrentUserId = userId;
- mWindowManager.setCurrentUser(userId);
+ updateRelatedUserIdsLocked();
+ mWindowManager.setCurrentUser(userId, mRelatedUserIds);
// Once the internal notion of the active user has switched, we lock the device
// with the option to show the user switcher on the keyguard.
mWindowManager.lockNow(null);
} else {
final Integer currentUserIdInt = Integer.valueOf(mCurrentUserId);
+ updateRelatedUserIdsLocked();
+ mWindowManager.updateRelatedUserIds(mRelatedUserIds);
mUserLru.remove(currentUserIdInt);
mUserLru.add(currentUserIdInt);
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 922cef4..087ad83c 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -71,6 +71,7 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.util.EventLog;
@@ -341,8 +342,19 @@
mCurrentUser = mService.mCurrentUserId;
}
- boolean okToShow(ActivityRecord r) {
- return r.userId == mCurrentUser
+ /**
+ * Checks whether the userid is either the current user or a related user.
+ */
+ private boolean isRelatedToOrCurrentUserLocked(int userId) {
+ if (mCurrentUser == userId) return true;
+ for (int i = 0; i < mService.mRelatedUserIds.length; i++) {
+ if (mService.mRelatedUserIds[i] == userId) return true;
+ }
+ return false;
+ }
+
+ boolean okToShowLocked(ActivityRecord r) {
+ return isRelatedToOrCurrentUserLocked(r.userId)
|| (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0;
}
@@ -362,7 +374,7 @@
final ArrayList<ActivityRecord> activities = task.mActivities;
for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
ActivityRecord r = activities.get(activityNdx);
- if (!r.finishing && !r.delayedResume && r != notTop && okToShow(r)) {
+ if (!r.finishing && !r.delayedResume && r != notTop && okToShowLocked(r)) {
return r;
}
}
@@ -389,7 +401,7 @@
for (int i = activities.size() - 1; i >= 0; --i) {
final ActivityRecord r = activities.get(i);
// Note: the taskId check depends on real taskId fields being non-zero
- if (!r.finishing && (token != r.appToken) && okToShow(r)) {
+ if (!r.finishing && (token != r.appToken) && okToShowLocked(r)) {
return r;
}
}
@@ -542,7 +554,7 @@
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
TaskRecord task = mTaskHistory.get(taskNdx);
- if (task.userId != mCurrentUser) {
+ if (!isRelatedToOrCurrentUserLocked(task.userId)) {
return null;
}
final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -573,7 +585,7 @@
int index = mTaskHistory.size();
for (int i = 0; i < index; ) {
TaskRecord task = mTaskHistory.get(i);
- if (task.userId == userId) {
+ if (isRelatedToOrCurrentUserLocked(task.userId)) {
if (DEBUG_TASKS) Slog.d(TAG, "switchUserLocked: stack=" + getStackId() +
" moving " + task + " to top");
mTaskHistory.remove(i);
@@ -704,10 +716,17 @@
int w = mThumbnailWidth;
int h = mThumbnailHeight;
if (w < 0) {
- mThumbnailWidth = w =
- res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
- mThumbnailHeight = h =
- res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
+ if (SystemProperties.getBoolean("persist.recents.use_alternate", false)) {
+ mThumbnailWidth = w =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.recents_thumbnail_width);
+ mThumbnailHeight = h =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.recents_thumbnail_height);
+ } else {
+ mThumbnailWidth = w =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
+ mThumbnailHeight = h =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
+ }
}
if (w > 0) {
@@ -1728,10 +1747,10 @@
mTaskHistory.remove(task);
// Now put task at top.
int stackNdx = mTaskHistory.size();
- if (task.userId != mCurrentUser) {
+ if (!isRelatedToOrCurrentUserLocked(task.userId)) {
// Put non-current user tasks below current user tasks.
while (--stackNdx >= 0) {
- if (mTaskHistory.get(stackNdx).userId != mCurrentUser) {
+ if (!isRelatedToOrCurrentUserLocked(mTaskHistory.get(stackNdx).userId)) {
break;
}
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 9740812..3a43521 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -153,7 +153,7 @@
ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
ActivityRecord r = mActivities.get(activityNdx);
- if (!r.finishing && r != notTop && stack.okToShow(r)) {
+ if (!r.finishing && r != notTop && stack.okToShowLocked(r)) {
return r;
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index e49382e..316bd57 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -52,6 +52,7 @@
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.KeyboardLayout;
+import android.hardware.input.TouchCalibration;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
@@ -76,6 +77,7 @@
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.PointerIcon;
+import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicy;
import android.widget.Toast;
@@ -183,6 +185,7 @@
InputChannel fromChannel, InputChannel toChannel);
private static native void nativeSetPointerSpeed(long ptr, int speed);
private static native void nativeSetShowTouches(long ptr, boolean enabled);
+ private static native void nativeReloadCalibration(long ptr);
private static native void nativeVibrate(long ptr, int deviceId, long[] pattern,
int repeat, int token);
private static native void nativeCancelVibrate(long ptr, int deviceId, int token);
@@ -700,6 +703,47 @@
mTempFullKeyboards.clear();
}
+ @Override // Binder call & native callback
+ public TouchCalibration getTouchCalibrationForInputDevice(String inputDeviceDescriptor,
+ int surfaceRotation) {
+ if (inputDeviceDescriptor == null) {
+ throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+ }
+
+ synchronized (mDataStore) {
+ return mDataStore.getTouchCalibration(inputDeviceDescriptor, surfaceRotation);
+ }
+ }
+
+ @Override // Binder call
+ public void setTouchCalibrationForInputDevice(String inputDeviceDescriptor, int surfaceRotation,
+ TouchCalibration calibration) {
+ if (!checkCallingPermission(android.Manifest.permission.SET_INPUT_CALIBRATION,
+ "setTouchCalibrationForInputDevice()")) {
+ throw new SecurityException("Requires SET_INPUT_CALIBRATION permission");
+ }
+ if (inputDeviceDescriptor == null) {
+ throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+ }
+ if (calibration == null) {
+ throw new IllegalArgumentException("calibration must not be null");
+ }
+ if (surfaceRotation < Surface.ROTATION_0 || surfaceRotation > Surface.ROTATION_270) {
+ throw new IllegalArgumentException("surfaceRotation value out of bounds");
+ }
+
+ synchronized (mDataStore) {
+ try {
+ if (mDataStore.setTouchCalibration(inputDeviceDescriptor, surfaceRotation,
+ calibration)) {
+ nativeReloadCalibration(mPtr);
+ }
+ } finally {
+ mDataStore.saveIfNeeded();
+ }
+ }
+ }
+
// Must be called on handler.
private void showMissingKeyboardLayoutNotification() {
if (!mKeyboardLayoutNotificationShown) {
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index 71de776..92fa813 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -24,6 +24,8 @@
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import android.view.Surface;
+import android.hardware.input.TouchCalibration;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.Xml;
@@ -82,6 +84,30 @@
}
}
+ public TouchCalibration getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+ if (state == null) {
+ return TouchCalibration.IDENTITY;
+ }
+
+ TouchCalibration cal = state.getTouchCalibration(surfaceRotation);
+ if (cal == null) {
+ return TouchCalibration.IDENTITY;
+ }
+ return cal;
+ }
+
+ public boolean setTouchCalibration(String inputDeviceDescriptor, int surfaceRotation, TouchCalibration calibration) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+
+ if (state.setTouchCalibration(surfaceRotation, calibration)) {
+ setDirty();
+ return true;
+ }
+
+ return false;
+ }
+
public String getCurrentKeyboardLayout(String inputDeviceDescriptor) {
InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
return state != null ? state.getCurrentKeyboardLayout() : null;
@@ -275,9 +301,35 @@
}
private static final class InputDeviceState {
+ private static final String[] CALIBRATION_NAME = { "x_scale",
+ "x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" };
+
+ private TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
private String mCurrentKeyboardLayout;
private ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
+ public TouchCalibration getTouchCalibration(int surfaceRotation) {
+ try {
+ return mTouchCalibration[surfaceRotation];
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ Slog.w(InputManagerService.TAG, "Cannot get touch calibration.", ex);
+ return null;
+ }
+ }
+
+ public boolean setTouchCalibration(int surfaceRotation, TouchCalibration calibration) {
+ try {
+ if (!calibration.equals(mTouchCalibration[surfaceRotation])) {
+ mTouchCalibration[surfaceRotation] = calibration;
+ return true;
+ }
+ return false;
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ Slog.w(InputManagerService.TAG, "Cannot set touch calibration.", ex);
+ return false;
+ }
+ }
+
public String getCurrentKeyboardLayout() {
return mCurrentKeyboardLayout;
}
@@ -389,6 +441,52 @@
}
mCurrentKeyboardLayout = descriptor;
}
+ } else if (parser.getName().equals("calibration")) {
+ String format = parser.getAttributeValue(null, "format");
+ String rotation = parser.getAttributeValue(null, "rotation");
+ int r = -1;
+
+ if (format == null) {
+ throw new XmlPullParserException(
+ "Missing format attribute on calibration.");
+ }
+ if (!format.equals("affine")) {
+ throw new XmlPullParserException(
+ "Unsupported format for calibration.");
+ }
+ if (rotation != null) {
+ try {
+ r = stringToSurfaceRotation(rotation);
+ } catch (IllegalArgumentException e) {
+ throw new XmlPullParserException(
+ "Unsupported rotation for calibration.");
+ }
+ }
+
+ float[] matrix = TouchCalibration.IDENTITY.getAffineTransform();
+ int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ String tag = parser.getName().toLowerCase();
+ String value = parser.nextText();
+
+ for (int i = 0; i < matrix.length && i < CALIBRATION_NAME.length; i++) {
+ if (tag.equals(CALIBRATION_NAME[i])) {
+ matrix[i] = Float.parseFloat(value);
+ break;
+ }
+ }
+ }
+
+ if (r == -1) {
+ // Assume calibration applies to all rotations
+ for (r = 0; r < mTouchCalibration.length; r++) {
+ mTouchCalibration[r] = new TouchCalibration(matrix[0],
+ matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
+ }
+ } else {
+ mTouchCalibration[r] = new TouchCalibration(matrix[0],
+ matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
+ }
}
}
@@ -411,6 +509,49 @@
}
serializer.endTag(null, "keyboard-layout");
}
+
+ for (int i = 0; i < mTouchCalibration.length; i++) {
+ if (mTouchCalibration[i] != null) {
+ String rotation = surfaceRotationToString(i);
+ float[] transform = mTouchCalibration[i].getAffineTransform();
+
+ serializer.startTag(null, "calibration");
+ serializer.attribute(null, "format", "affine");
+ serializer.attribute(null, "rotation", rotation);
+ for (int j = 0; j < transform.length && j < CALIBRATION_NAME.length; j++) {
+ serializer.startTag(null, CALIBRATION_NAME[j]);
+ serializer.text(Float.toString(transform[j]));
+ serializer.endTag(null, CALIBRATION_NAME[j]);
+ }
+ serializer.endTag(null, "calibration");
+ }
+ }
+ }
+
+ private static String surfaceRotationToString(int surfaceRotation) {
+ switch (surfaceRotation) {
+ case Surface.ROTATION_0: return "0";
+ case Surface.ROTATION_90: return "90";
+ case Surface.ROTATION_180: return "180";
+ case Surface.ROTATION_270: return "270";
+ }
+ throw new IllegalArgumentException("Unsupported surface rotation value" + surfaceRotation);
+ }
+
+ private static int stringToSurfaceRotation(String s) {
+ if ("0".equals(s)) {
+ return Surface.ROTATION_0;
+ }
+ if ("90".equals(s)) {
+ return Surface.ROTATION_90;
+ }
+ if ("180".equals(s)) {
+ return Surface.ROTATION_180;
+ }
+ if ("270".equals(s)) {
+ return Surface.ROTATION_270;
+ }
+ throw new IllegalArgumentException("Unsupported surface rotation string '" + s + "'");
}
}
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c006613..be94ca7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -19,7 +19,6 @@
import static android.view.WindowManager.LayoutParams.*;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-
import android.app.AppOpsManager;
import android.util.ArraySet;
import android.util.TimeUtils;
@@ -302,8 +301,16 @@
}
};
- // Current user when multi-user is enabled. Don't show windows of non-current user.
+ /**
+ * Current user when multi-user is enabled. Don't show windows of
+ * non-current user. Also see mRelatedUserIds.
+ */
int mCurrentUserId;
+ /**
+ * Users related to the current user. These are also allowed to show windows
+ * on the current user.
+ */
+ int[] mRelatedUserIds = new int[0];
final Context mContext;
@@ -5284,10 +5291,16 @@
mPolicy.setTouchExplorationEnabled(enabled);
}
- public void setCurrentUser(final int newUserId) {
+ public void updateRelatedUserIds(final int[] relatedUserIds) {
synchronized (mWindowMap) {
- int oldUserId = mCurrentUserId;
+ mRelatedUserIds = relatedUserIds;
+ }
+ }
+
+ public void setCurrentUser(final int newUserId, final int[] relatedUserIds) {
+ synchronized (mWindowMap) {
mCurrentUserId = newUserId;
+ mRelatedUserIds = relatedUserIds;
mAppTransition.setCurrentUser(newUserId);
mPolicy.setCurrentUserLw(newUserId);
@@ -5302,6 +5315,15 @@
}
}
+ /* Called by WindowState */
+ boolean isRelatedToOrCurrentUserLocked(int userId) {
+ if (userId == mCurrentUserId) return true;
+ for (int i = 0; i < mRelatedUserIds.length; i++) {
+ if (mRelatedUserIds[i] == userId) return true;
+ }
+ return false;
+ }
+
public void enableScreenAfterBoot() {
synchronized(mWindowMap) {
if (DEBUG_BOOT) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a8e45c4..2c0e99e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1249,7 +1249,7 @@
}
return win.mShowToOwnerOnly
- && UserHandle.getUserId(win.mOwnerUid) != mService.mCurrentUserId;
+ && !mService.isRelatedToOrCurrentUserLocked(UserHandle.getUserId(win.mOwnerUid));
}
private static void applyInsets(Region outRegion, Rect frame, Rect inset) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 0207c55..e1069ae 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -49,6 +49,7 @@
#include <android/graphics/GraphicsJNI.h>
#include <ScopedLocalRef.h>
+#include <ScopedPrimitiveArray.h>
#include <ScopedUtfChars.h>
#include "com_android_server_power_PowerManagerService.h"
@@ -86,6 +87,7 @@
jmethodID getPointerIcon;
jmethodID getKeyboardLayoutOverlay;
jmethodID getDeviceAlias;
+ jmethodID getTouchCalibrationForInputDevice;
} gServiceClassInfo;
static struct {
@@ -105,6 +107,11 @@
jmethodID constructor;
} gInputDeviceIdentifierInfo;
+static struct {
+ jclass clazz;
+ jmethodID getAffineTransform;
+} gTouchCalibrationClassInfo;
+
// --- Global functions ---
@@ -182,6 +189,7 @@
void setSystemUiVisibility(int32_t visibility);
void setPointerSpeed(int32_t speed);
void setShowTouches(bool enabled);
+ void reloadCalibration();
/* --- InputReaderPolicyInterface implementation --- */
@@ -190,6 +198,10 @@
virtual void notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices);
virtual sp<KeyCharacterMap> getKeyboardLayoutOverlay(const InputDeviceIdentifier& identifier);
virtual String8 getDeviceAlias(const InputDeviceIdentifier& identifier);
+ virtual TouchAffineTransformation getTouchAffineTransformation(JNIEnv *env,
+ jfloatArray matrixArr);
+ virtual TouchAffineTransformation getTouchAffineTransformation(
+ const String8& inputDeviceDescriptor, int32_t surfaceRotation);
/* --- InputDispatcherPolicyInterface implementation --- */
@@ -741,6 +753,11 @@
InputReaderConfiguration::CHANGE_SHOW_TOUCHES);
}
+void NativeInputManager::reloadCalibration() {
+ mInputManager->getReader()->requestRefreshConfiguration(
+ InputReaderConfiguration::TOUCH_AFFINE_TRANSFORMATION);
+}
+
bool NativeInputManager::isScreenOn() {
return android_server_PowerManagerService_isScreenOn();
}
@@ -749,6 +766,43 @@
return android_server_PowerManagerService_isScreenBright();
}
+TouchAffineTransformation NativeInputManager::getTouchAffineTransformation(
+ JNIEnv *env, jfloatArray matrixArr) {
+ ScopedFloatArrayRO matrix(env, matrixArr);
+ assert(matrix.size() == 6);
+
+ TouchAffineTransformation transform;
+ transform.x_scale = matrix[0];
+ transform.x_ymix = matrix[1];
+ transform.x_offset = matrix[2];
+ transform.y_xmix = matrix[3];
+ transform.y_scale = matrix[4];
+ transform.y_offset = matrix[5];
+
+ return transform;
+}
+
+TouchAffineTransformation NativeInputManager::getTouchAffineTransformation(
+ const String8& inputDeviceDescriptor, int32_t surfaceRotation) {
+ JNIEnv* env = jniEnv();
+
+ ScopedLocalRef<jstring> descriptorObj(env, env->NewStringUTF(inputDeviceDescriptor.string()));
+
+ jobject cal = env->CallObjectMethod(mServiceObj,
+ gServiceClassInfo.getTouchCalibrationForInputDevice, descriptorObj.get(),
+ surfaceRotation);
+
+ jfloatArray matrixArr = jfloatArray(env->CallObjectMethod(cal,
+ gTouchCalibrationClassInfo.getAffineTransform));
+
+ TouchAffineTransformation transform = getTouchAffineTransformation(env, matrixArr);
+
+ env->DeleteLocalRef(matrixArr);
+ env->DeleteLocalRef(cal);
+
+ return transform;
+}
+
bool NativeInputManager::filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) {
jobject inputEventObj;
@@ -1231,6 +1285,11 @@
im->setShowTouches(enabled);
}
+static void nativeReloadCalibration(JNIEnv* env, jclass clazz, jlong ptr) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ im->reloadCalibration();
+}
+
static void nativeVibrate(JNIEnv* env,
jclass clazz, jlong ptr, jint deviceId, jlongArray patternObj,
jint repeat, jint token) {
@@ -1336,6 +1395,8 @@
(void*) nativeSetPointerSpeed },
{ "nativeSetShowTouches", "(JZ)V",
(void*) nativeSetShowTouches },
+ { "nativeReloadCalibration", "(J)V",
+ (void*) nativeReloadCalibration },
{ "nativeVibrate", "(JI[JII)V",
(void*) nativeVibrate },
{ "nativeCancelVibrate", "(JII)V",
@@ -1446,6 +1507,10 @@
GET_METHOD_ID(gServiceClassInfo.getDeviceAlias, clazz,
"getDeviceAlias", "(Ljava/lang/String;)Ljava/lang/String;");
+ GET_METHOD_ID(gServiceClassInfo.getTouchCalibrationForInputDevice, clazz,
+ "getTouchCalibrationForInputDevice",
+ "(Ljava/lang/String;I)Landroid/hardware/input/TouchCalibration;");
+
// InputDevice
FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice");
@@ -1468,6 +1533,14 @@
GET_METHOD_ID(gInputDeviceIdentifierInfo.constructor, gInputDeviceIdentifierInfo.clazz,
"<init>", "(Ljava/lang/String;II)V");
+ // TouchCalibration
+
+ FIND_CLASS(gTouchCalibrationClassInfo.clazz, "android/hardware/input/TouchCalibration");
+ gTouchCalibrationClassInfo.clazz = jclass(env->NewGlobalRef(gTouchCalibrationClassInfo.clazz));
+
+ GET_METHOD_ID(gTouchCalibrationClassInfo.getAffineTransform, gTouchCalibrationClassInfo.clazz,
+ "getAffineTransform", "()[F");
+
return 0;
}