From 1b6a6b012dd1580045fc404e2c3266d739edb93d Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Mon, 11 Dec 2017 14:37:35 -0800 Subject: [PATCH 01/73] Proper autofill fix to let phone process autofill Settings activity. Test: adb shell am start com.android.settings/.RadioInfo Bug: 69981710 Fixes: 70506888 Change-Id: Id29bad2d20b621f7379eb6144c95dcc819949b3d Merged-In: Id29bad2d20b621f7379eb6144c95dcc819949b3d (cherry picked from commit 97f16a76db29269619d9a1b45d4cea49026a5b6a) (cherry picked from commit 92b5d2783a1b97bee476f04754481403839b4e45) --- .../android/app/ActivityManagerInternal.java | 5 +++++ .../autofill/AutofillManagerServiceImpl.java | 9 ++++++--- .../server/am/ActivityManagerService.java | 20 +++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index c8d983933fc6d..9dceb7f9e4339 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -268,4 +268,9 @@ public abstract int startActivitiesAsPackage(String packageName, * @param token The IApplicationToken for the activity */ public abstract void setFocusedActivity(IBinder token); + + /** + * Returns {@code true} if {@code uid} is running an activity from {@code packageName}. + */ + public abstract boolean hasRunningActivity(int uid, @Nullable String packageName); } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index a17c3ca92e5dd..6d3398ea2648b 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -26,6 +26,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.IActivityManager; import android.content.ComponentName; @@ -69,6 +70,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.HandlerCaller; +import com.android.server.LocalServices; import com.android.server.autofill.ui.AutoFillUI; import java.io.PrintWriter; @@ -417,16 +419,17 @@ private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int u * Asserts the component is owned by the caller. */ private void assertCallerLocked(@NonNull ComponentName componentName) { + final String packageName = componentName.getPackageName(); final PackageManager pm = mContext.getPackageManager(); final int callingUid = Binder.getCallingUid(); final int packageUid; try { - packageUid = pm.getPackageUidAsUser(componentName.getPackageName(), - UserHandle.getCallingUserId()); + packageUid = pm.getPackageUidAsUser(packageName, UserHandle.getCallingUserId()); } catch (NameNotFoundException e) { throw new SecurityException("Could not verify UID for " + componentName); } - if (callingUid != packageUid) { + if (callingUid != packageUid && !LocalServices.getService(ActivityManagerInternal.class) + .hasRunningActivity(callingUid, packageName)) { final String[] packages = pm.getPackagesForUid(callingUid); final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid; Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 1520b96a4afd4..4a2d9c9ca5a48 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -24248,6 +24248,26 @@ public void setFocusedActivity(IBinder token) { } } } + + @Override + public boolean hasRunningActivity(int uid, @Nullable String packageName) { + if (packageName == null) return false; + + synchronized (ActivityManagerService.this) { + for (int i = 0; i < mLruProcesses.size(); i++) { + final ProcessRecord processRecord = mLruProcesses.get(i); + if (processRecord.uid == uid) { + for (int j = 0; j < processRecord.activities.size(); j++) { + final ActivityRecord activityRecord = processRecord.activities.get(j); + if (packageName.equals(activityRecord.packageName)) { + return true; + } + } + } + } + } + return false; + } } /** From 4708368686076eaa9ca24651b7e75436e7507ecd Mon Sep 17 00:00:00 2001 From: Marco Nelissen Date: Tue, 7 Nov 2017 13:52:02 -0800 Subject: [PATCH 02/73] Rework thumbnail cleanup Bug: 63766886 Test: ran CTS tests Change-Id: I1f92bb014e275eafe3f42aef1f8c817f187c6608 (cherry picked from commit 6d2096f3889d38da60099b1b5678347de4f042bf) --- core/java/android/provider/MediaStore.java | 4 +- media/java/android/media/MediaScanner.java | 52 -------------------- media/java/android/media/MiniThumbFile.java | 54 ++++++++++++++++++++- 3 files changed, 54 insertions(+), 56 deletions(-) diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 13e1e26b51c33..ff4f358529a41 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -694,8 +694,8 @@ static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, int ki // Log.v(TAG, "getThumbnail: origId="+origId+", kind="+kind+", isVideo="+isVideo); // If the magic is non-zero, we simply return thumbnail if it does exist. // querying MediaProvider and simply return thumbnail. - MiniThumbFile thumbFile = new MiniThumbFile(isVideo ? Video.Media.EXTERNAL_CONTENT_URI - : Images.Media.EXTERNAL_CONTENT_URI); + MiniThumbFile thumbFile = MiniThumbFile.instance( + isVideo ? Video.Media.EXTERNAL_CONTENT_URI : Images.Media.EXTERNAL_CONTENT_URI); Cursor c = null; try { long magic = thumbFile.getMagic(origId); diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index cb4e46fe945a3..e66945bd7ec46 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -323,7 +323,6 @@ public class MediaScanner implements AutoCloseable { private final Uri mAudioUri; private final Uri mVideoUri; private final Uri mImagesUri; - private final Uri mThumbsUri; private final Uri mPlaylistsUri; private final Uri mFilesUri; private final Uri mFilesUriNoNotify; @@ -419,7 +418,6 @@ public MediaScanner(Context c, String volumeName) { mAudioUri = Audio.Media.getContentUri(volumeName); mVideoUri = Video.Media.getContentUri(volumeName); mImagesUri = Images.Media.getContentUri(volumeName); - mThumbsUri = Images.Thumbnails.getContentUri(volumeName); mFilesUri = Files.getContentUri(volumeName); mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build(); @@ -1283,53 +1281,6 @@ private void prescan(String filePath, boolean prescanFiles) throws RemoteExcepti } } - private void pruneDeadThumbnailFiles() { - HashSet existingFiles = new HashSet(); - String directory = "/sdcard/DCIM/.thumbnails"; - String [] files = (new File(directory)).list(); - Cursor c = null; - if (files == null) - files = new String[0]; - - for (int i = 0; i < files.length; i++) { - String fullPathString = directory + "/" + files[i]; - existingFiles.add(fullPathString); - } - - try { - c = mMediaProvider.query( - mThumbsUri, - new String [] { "_data" }, - null, - null, - null, null); - Log.v(TAG, "pruneDeadThumbnailFiles... " + c); - if (c != null && c.moveToFirst()) { - do { - String fullPathString = c.getString(0); - existingFiles.remove(fullPathString); - } while (c.moveToNext()); - } - - for (String fileToDelete : existingFiles) { - if (false) - Log.v(TAG, "fileToDelete is " + fileToDelete); - try { - (new File(fileToDelete)).delete(); - } catch (SecurityException ex) { - } - } - - Log.v(TAG, "/pruneDeadThumbnailFiles... " + c); - } catch (RemoteException e) { - // We will soon be killed... - } finally { - if (c != null) { - c.close(); - } - } - } - static class MediaBulkDeleter { StringBuilder whereClause = new StringBuilder(); ArrayList whereArgs = new ArrayList(100); @@ -1373,9 +1324,6 @@ private void postscan(final String[] directories) throws RemoteException { processPlayLists(); } - if (mOriginalCount == 0 && mImagesUri.equals(Images.Media.getContentUri("external"))) - pruneDeadThumbnailFiles(); - // allow GC to clean up mPlayLists.clear(); } diff --git a/media/java/android/media/MiniThumbFile.java b/media/java/android/media/MiniThumbFile.java index 664308c45bf9b..98993676ce43d 100644 --- a/media/java/android/media/MiniThumbFile.java +++ b/media/java/android/media/MiniThumbFile.java @@ -44,13 +44,14 @@ */ public class MiniThumbFile { private static final String TAG = "MiniThumbFile"; - private static final int MINI_THUMB_DATA_FILE_VERSION = 3; + private static final int MINI_THUMB_DATA_FILE_VERSION = 4; public static final int BYTES_PER_MINTHUMB = 10000; private static final int HEADER_SIZE = 1 + 8 + 4; private Uri mUri; private RandomAccessFile mMiniThumbFile; private FileChannel mChannel; private ByteBuffer mBuffer; + private ByteBuffer mEmptyBuffer; private static final Hashtable sThumbFiles = new Hashtable(); @@ -127,9 +128,10 @@ private RandomAccessFile miniThumbDataFile() { return mMiniThumbFile; } - public MiniThumbFile(Uri uri) { + private MiniThumbFile(Uri uri) { mUri = uri; mBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB); + mEmptyBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB); } public synchronized void deactivate() { @@ -184,6 +186,54 @@ public synchronized long getMagic(long id) { return 0; } + public synchronized void eraseMiniThumb(long id) { + RandomAccessFile r = miniThumbDataFile(); + if (r != null) { + long pos = id * BYTES_PER_MINTHUMB; + FileLock lock = null; + try { + mBuffer.clear(); + mBuffer.limit(1 + 8); + + lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, false); + // check that we can read the following 9 bytes + // (1 for the "status" and 8 for the long) + if (mChannel.read(mBuffer, pos) == 9) { + mBuffer.position(0); + if (mBuffer.get() == 1) { + long currentMagic = mBuffer.getLong(); + if (currentMagic == 0) { + // there is no thumbnail stored here + Log.i(TAG, "no thumbnail for id " + id); + return; + } + // zero out the thumbnail slot + // Log.v(TAG, "clearing slot " + id + ", magic " + currentMagic + // + " at offset " + pos); + mChannel.write(mEmptyBuffer, pos); + } + } else { + // Log.v(TAG, "No slot"); + } + } catch (IOException ex) { + Log.v(TAG, "Got exception checking file magic: ", ex); + } catch (RuntimeException ex) { + // Other NIO related exception like disk full, read only channel..etc + Log.e(TAG, "Got exception when reading magic, id = " + id + + ", disk full or mount read-only? " + ex.getClass()); + } finally { + try { + if (lock != null) lock.release(); + } + catch (IOException ex) { + // ignore it. + } + } + } else { + // Log.v(TAG, "No data file"); + } + } + public synchronized void saveMiniThumbToFile(byte[] data, long id, long magic) throws IOException { RandomAccessFile r = miniThumbDataFile(); From fda6995fa32be4a2d8b0f0b421f3ca47c3e7853f Mon Sep 17 00:00:00 2001 From: Fyodor Kupolov Date: Fri, 16 Mar 2018 12:20:40 -0700 Subject: [PATCH 03/73] Use concrete CREATOR instance for parceling lists Replaced readTypedArrayList/writeTypedArrayList with writeTypedList/createTypedArrayList(CREATOR) Bug: 71508348 Test: CtsAutoFillServiceTestCases pass Merged-In: I2a8321023b40cc74b7026eb0fb32a9cc5f5543a9 Change-Id: Id17d02e40a4ae567bf2d74d2ea8ba4d8a943bdb7 (cherry picked from commit 4921986db76b1580bcb6ec8b2fd381d1364a6325) --- core/java/android/os/Parcel.java | 15 ++++++++------- core/java/android/service/autofill/Dataset.java | 9 +++++---- .../android/service/autofill/SaveRequest.java | 8 ++++---- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index fae9d5310f8ed..c6d3860c66006 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -1340,6 +1340,13 @@ public final void readBinderArray(IBinder[] val) { * @see Parcelable */ public final void writeTypedList(List val) { + writeTypedList(val, 0); + } + + /** + * @hide + */ + public void writeTypedList(List val, int parcelableFlags) { if (val == null) { writeInt(-1); return; @@ -1348,13 +1355,7 @@ public final void writeTypedList(List val) { int i=0; writeInt(N); while (i < N) { - T item = val.get(i); - if (item != null) { - writeInt(1); - item.writeToParcel(this, 0); - } else { - writeInt(0); - } + writeTypedObject(val.get(i), parcelableFlags); i++; } } diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index 65b0efcbe032a..21a3df89e0f83 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -316,8 +316,8 @@ public int describeContents() { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeParcelable(mPresentation, flags); - parcel.writeTypedArrayList(mFieldIds, flags); - parcel.writeTypedArrayList(mFieldValues, flags); + parcel.writeTypedList(mFieldIds, flags); + parcel.writeTypedList(mFieldValues, flags); parcel.writeParcelableList(mFieldPresentations, flags); parcel.writeParcelable(mAuthentication, flags); parcel.writeString(mId); @@ -333,8 +333,9 @@ public Dataset createFromParcel(Parcel parcel) { final Builder builder = (presentation == null) ? new Builder() : new Builder(presentation); - final ArrayList ids = parcel.readTypedArrayList(null); - final ArrayList values = parcel.readTypedArrayList(null); + final ArrayList ids = parcel.createTypedArrayList(AutofillId.CREATOR); + final ArrayList values = + parcel.createTypedArrayList(AutofillValue.CREATOR); final ArrayList presentations = new ArrayList<>(); parcel.readParcelableList(presentations, null); final int idCount = (ids != null) ? ids.size() : 0; diff --git a/core/java/android/service/autofill/SaveRequest.java b/core/java/android/service/autofill/SaveRequest.java index 9de931542cb91..fc4272dd7742c 100644 --- a/core/java/android/service/autofill/SaveRequest.java +++ b/core/java/android/service/autofill/SaveRequest.java @@ -19,9 +19,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; -import android.os.CancellationSignal; import android.os.Parcel; import android.os.Parcelable; + import com.android.internal.util.Preconditions; import java.util.ArrayList; @@ -45,7 +45,7 @@ public SaveRequest(@NonNull ArrayList fillContexts, } private SaveRequest(@NonNull Parcel parcel) { - this(parcel.readTypedArrayList(null), parcel.readBundle()); + this(parcel.createTypedArrayList(FillContext.CREATOR), parcel.readBundle()); } /** @@ -57,7 +57,7 @@ private SaveRequest(@NonNull Parcel parcel) { /** * Gets the extra client state returned from the last {@link - * AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)} + * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)} * fill request}. * * @return The client state. @@ -73,7 +73,7 @@ public int describeContents() { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeTypedArrayList(mFillContexts, flags); + parcel.writeTypedList(mFillContexts, flags); parcel.writeBundle(mClientState); } From d5e98d935225425125dcdbe5f203d6ae1f6375b3 Mon Sep 17 00:00:00 2001 From: akirilov Date: Tue, 27 Mar 2018 13:08:47 -0700 Subject: [PATCH 04/73] RESTRICT AUTOMERGE: Prevent reporting fake package name - framework (backport to oc-mr1-dev) Test: added AccessibilityEndToEndTest#testPackageNameCannotBeFaked cts-tradefed run cts -m CtsAccessibilityServiceTestCases cts-tradefed run cts -m CtsAccessibilityTestCases Bug: 69981755 Change-Id: If3752e106aa7fdee4645dc9852289af471ceff18 Merged-In: I13304efbee10d1affa087e9c8bc4ec237643283e (cherry picked from commit c36db6d473c9988496cd614924ee113b67f7e333) --- .../IAccessibilityServiceConnection.aidl | 10 +- .../appwidget/AppWidgetManagerInternal.java | 36 +++ core/java/android/view/ViewRootImpl.java | 1 + .../AccessibilityInteractionClient.java | 115 ++++--- .../accessibility/AccessibilityManager.java | 5 +- .../accessibility/IAccessibilityManager.aidl | 2 +- .../AccessibilityManagerService.java | 288 +++++++++++++----- .../appwidget/AppWidgetServiceImpl.java | 25 ++ 8 files changed, 360 insertions(+), 122 deletions(-) create mode 100644 core/java/android/appwidget/AppWidgetManagerInternal.java diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 7a1931718888c..037aeb058f15c 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -35,23 +35,23 @@ interface IAccessibilityServiceConnection { void setServiceInfo(in AccessibilityServiceInfo info); - boolean findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, + String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, long threadId, in Bundle arguments); - boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId, + String[] findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); - boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId, + String[] findAccessibilityNodeInfosByViewId(int accessibilityWindowId, long accessibilityNodeId, String viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); - boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType, + String[] findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); - boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction, + String[] focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId, diff --git a/core/java/android/appwidget/AppWidgetManagerInternal.java b/core/java/android/appwidget/AppWidgetManagerInternal.java new file mode 100644 index 0000000000000..5562c550fe188 --- /dev/null +++ b/core/java/android/appwidget/AppWidgetManagerInternal.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 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.appwidget; + +import android.annotation.Nullable; +import android.util.ArraySet; + +/** + * App widget manager local system service interface. + * + * @hide Only for use within the system server. + */ +public abstract class AppWidgetManagerInternal { + + /** + * Gets the packages from which the uid hosts widgets. + * + * @param uid The potential host UID. + * @return Whether the UID hosts widgets from the package. + */ + public abstract @Nullable ArraySet getHostedWidgetPackages(int uid); +} diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 8f250a9e9f15b..3f1ea34c37ab0 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -7733,6 +7733,7 @@ public void ensureConnection() { if (!registered) { mAttachInfo.mAccessibilityWindowId = mAccessibilityManager.addAccessibilityInteractionConnection(mWindow, + mContext.getPackageName(), new AccessibilityInteractionConnection(ViewRootImpl.this)); } } diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 19213ca06c5ec..be3b34d0ccf8a 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -28,6 +28,8 @@ import android.util.LongSparseArray; import android.util.SparseArray; +import com.android.internal.util.ArrayUtils; + import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -283,14 +285,19 @@ public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int conn } final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId( - accessibilityWindowId, accessibilityNodeId, interactionId, this, - prefetchFlags, Thread.currentThread().getId(), arguments); - Binder.restoreCallingIdentity(identityToken); - if (success) { + final String[] packageNames; + try { + packageNames = connection.findAccessibilityNodeInfoByAccessibilityId( + accessibilityWindowId, accessibilityNodeId, interactionId, this, + prefetchFlags, Thread.currentThread().getId(), arguments); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + if (packageNames != null) { List infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, + bypassCache, packageNames); if (infos != null && !infos.isEmpty()) { for (int i = 1; i < infos.size(); i++) { infos.get(i).recycle(); @@ -333,15 +340,21 @@ public List findAccessibilityNodeInfosByViewId(int connec if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success = connection.findAccessibilityNodeInfosByViewId( - accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this, - Thread.currentThread().getId()); - Binder.restoreCallingIdentity(identityToken); - if (success) { + final String[] packageNames; + try { + packageNames = connection.findAccessibilityNodeInfosByViewId( + accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this, + Thread.currentThread().getId()); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + + if (packageNames != null) { List infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); if (infos != null) { - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, + false, packageNames); return infos; } } @@ -381,15 +394,21 @@ public List findAccessibilityNodeInfosByText(int connecti if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success = connection.findAccessibilityNodeInfosByText( - accessibilityWindowId, accessibilityNodeId, text, interactionId, this, - Thread.currentThread().getId()); - Binder.restoreCallingIdentity(identityToken); - if (success) { + final String[] packageNames; + try { + packageNames = connection.findAccessibilityNodeInfosByText( + accessibilityWindowId, accessibilityNodeId, text, interactionId, this, + Thread.currentThread().getId()); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + + if (packageNames != null) { List infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); if (infos != null) { - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, + false, packageNames); return infos; } } @@ -428,14 +447,19 @@ public AccessibilityNodeInfo findFocus(int connectionId, int accessibilityWindow if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success = connection.findFocus(accessibilityWindowId, - accessibilityNodeId, focusType, interactionId, this, - Thread.currentThread().getId()); - Binder.restoreCallingIdentity(identityToken); - if (success) { + final String[] packageNames; + try { + packageNames = connection.findFocus(accessibilityWindowId, + accessibilityNodeId, focusType, interactionId, this, + Thread.currentThread().getId()); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + + if (packageNames != null) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false, packageNames); return info; } } else { @@ -472,14 +496,19 @@ public AccessibilityNodeInfo focusSearch(int connectionId, int accessibilityWind if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success = connection.focusSearch(accessibilityWindowId, - accessibilityNodeId, direction, interactionId, this, - Thread.currentThread().getId()); - Binder.restoreCallingIdentity(identityToken); - if (success) { + final String[] packageNames; + try { + packageNames = connection.focusSearch(accessibilityWindowId, + accessibilityNodeId, direction, interactionId, this, + Thread.currentThread().getId()); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + + if (packageNames != null) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false, packageNames); return info; } } else { @@ -580,7 +609,7 @@ private List getFindAccessibilityNodeInfosResultAndClear( int interactionId) { synchronized (mInstanceLock) { final boolean success = waitForResultTimedLocked(interactionId); - List result = null; + final List result; if (success) { result = mFindAccessibilityNodeInfosResult; } else { @@ -696,13 +725,25 @@ private boolean waitForResultTimedLocked(int interactionId) { * * @param info The info. * @param connectionId The id of the connection to the system. + * @param bypassCache Whether or not to bypass the cache. The node is added to the cache if + * this value is {@code false} + * @param packageNames The valid package names a node can come from. */ private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, - int connectionId) { + int connectionId, boolean bypassCache, String[] packageNames) { if (info != null) { info.setConnectionId(connectionId); + // Empty array means any package name is Okay + if (!ArrayUtils.isEmpty(packageNames) + && !ArrayUtils.contains(packageNames, info.getPackageName().toString())) { + // If the node package not one of the valid ones, pick the top one - this + // is one of the packages running in the introspected UID. + info.setPackageName(packageNames[0]); + } info.setSealed(true); - sAccessibilityCache.add(info); + if (!bypassCache) { + sAccessibilityCache.add(info); + } } } @@ -711,14 +752,18 @@ private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, * * @param infos The {@link AccessibilityNodeInfo}s. * @param connectionId The id of the connection to the system. + * @param bypassCache Whether or not to bypass the cache. The nodes are added to the cache if + * this value is {@code false} + * @param packageNames The valid package names a node can come from. */ private void finalizeAndCacheAccessibilityNodeInfos(List infos, - int connectionId) { + int connectionId, boolean bypassCache, String[] packageNames) { if (infos != null) { final int infosCount = infos.size(); for (int i = 0; i < infosCount; i++) { AccessibilityNodeInfo info = infos.get(i); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, + bypassCache, packageNames); } } } diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 0b9bc5760fa86..2478281efcd17 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -885,7 +885,7 @@ public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName( * @hide */ public int addAccessibilityInteractionConnection(IWindow windowToken, - IAccessibilityInteractionConnection connection) { + String packageName, IAccessibilityInteractionConnection connection) { final IAccessibilityManager service; final int userId; synchronized (mLock) { @@ -896,7 +896,8 @@ public int addAccessibilityInteractionConnection(IWindow windowToken, userId = mUserId; } try { - return service.addAccessibilityInteractionConnection(windowToken, connection, userId); + return service.addAccessibilityInteractionConnection(windowToken, connection, + packageName, userId); } catch (RemoteException re) { Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); } diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 3f499abd2e4d1..6329c1141bd59 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -45,7 +45,7 @@ interface IAccessibilityManager { List getEnabledAccessibilityServiceList(int feedbackType, int userId); int addAccessibilityInteractionConnection(IWindow windowToken, - in IAccessibilityInteractionConnection connection, int userId); + in IAccessibilityInteractionConnection connection, String packageName, int userId); void removeAccessibilityInteractionConnection(IWindow windowToken); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 83dfccb57ebf3..d0d65d94fb5b6 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -34,6 +34,7 @@ import android.app.PendingIntent; import android.app.StatusBarManager; import android.app.UiAutomation; +import android.appwidget.AppWidgetManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -82,6 +83,7 @@ import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; +import android.util.ArraySet; import android.view.Display; import android.view.IWindow; import android.view.InputDevice; @@ -110,10 +112,11 @@ import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.internal.util.IntPair; +import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; import com.android.server.policy.AccessibilityShortcutController; import com.android.server.statusbar.StatusBarManagerInternal; - +import libcore.util.EmptyArray; import org.xmlpull.v1.XmlPullParserException; import java.io.FileDescriptor; @@ -199,6 +202,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private final WindowManagerInternal mWindowManagerService; + private AppWidgetManagerInternal mAppWidgetService; + private final SecurityPolicy mSecurityPolicy; private final MainHandler mMainHandler; @@ -229,10 +234,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private final RemoteCallbackList mGlobalClients = new RemoteCallbackList<>(); - private final SparseArray mGlobalInteractionConnections = + private final SparseArray mGlobalInteractionConnections = new SparseArray<>(); - private AccessibilityConnectionWrapper mPictureInPictureActionReplacingConnection; + private RemoteAccessibilityConnection mPictureInPictureActionReplacingConnection; private final SparseArray mGlobalWindowTokens = new SparseArray<>(); @@ -501,6 +506,11 @@ public void sendAccessibilityEvent(AccessibilityEvent event, int userId) { // performs the current profile parent resolution.. final int resolvedUserId = mSecurityPolicy .resolveCallingUserIdEnforcingPermissionsLocked(userId); + + // Make sure the reported package is one the caller has access to. + event.setPackageName(mSecurityPolicy.resolveValidReportedPackageLocked( + event.getPackageName(), UserHandle.getCallingAppId(), resolvedUserId)); + // This method does nothing for a background user. if (resolvedUserId == mCurrentUserId) { if (mSecurityPolicy.canDispatchAccessibilityEventLocked(event)) { @@ -627,30 +637,38 @@ public void interrupt(int userId) { @Override public int addAccessibilityInteractionConnection(IWindow windowToken, - IAccessibilityInteractionConnection connection, int userId) throws RemoteException { + IAccessibilityInteractionConnection connection, String packageName, + int userId) throws RemoteException { synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below // performs the current profile parent resolution. final int resolvedUserId = mSecurityPolicy .resolveCallingUserIdEnforcingPermissionsLocked(userId); + final int resolvedUid = UserHandle.getUid(resolvedUserId, UserHandle.getCallingAppId()); + + // Make sure the reported package is one the caller has access to. + packageName = mSecurityPolicy.resolveValidReportedPackageLocked( + packageName, UserHandle.getCallingAppId(), resolvedUserId); + final int windowId = sNextWindowId++; // If the window is from a process that runs across users such as // the system UI or the system we add it to the global state that // is shared across users. if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) { - AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper( - windowId, connection, UserHandle.USER_ALL); + RemoteAccessibilityConnection wrapper = new RemoteAccessibilityConnection( + windowId, connection, packageName, resolvedUid, UserHandle.USER_ALL); wrapper.linkToDeath(); mGlobalInteractionConnections.put(windowId, wrapper); mGlobalWindowTokens.put(windowId, windowToken.asBinder()); if (DEBUG) { Slog.i(LOG_TAG, "Added global connection for pid:" + Binder.getCallingPid() - + " with windowId: " + windowId + " and token: " + windowToken.asBinder()); + + " with windowId: " + windowId + " and token: " + + windowToken.asBinder()); } } else { - AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper( - windowId, connection, resolvedUserId); + RemoteAccessibilityConnection wrapper = new RemoteAccessibilityConnection( + windowId, connection, packageName, resolvedUid, resolvedUserId); wrapper.linkToDeath(); UserState userState = getUserStateLocked(resolvedUserId); userState.mInteractionConnections.put(windowId, wrapper); @@ -679,7 +697,8 @@ public void removeAccessibilityInteractionConnection(IWindow window) { if (removedWindowId >= 0) { if (DEBUG) { Slog.i(LOG_TAG, "Removed global connection for pid:" + Binder.getCallingPid() - + " with windowId: " + removedWindowId + " and token: " + window.asBinder()); + + " with windowId: " + removedWindowId + " and token: " + + window.asBinder()); } return; } @@ -703,13 +722,13 @@ public void removeAccessibilityInteractionConnection(IWindow window) { private int removeAccessibilityInteractionConnectionInternalLocked(IBinder windowToken, SparseArray windowTokens, - SparseArray interactionConnections) { + SparseArray interactionConnections) { final int count = windowTokens.size(); for (int i = 0; i < count; i++) { if (windowTokens.valueAt(i) == windowToken) { final int windowId = windowTokens.keyAt(i); windowTokens.removeAt(i); - AccessibilityConnectionWrapper wrapper = interactionConnections.get(windowId); + RemoteAccessibilityConnection wrapper = interactionConnections.get(windowId); wrapper.unlinkToDeath(); interactionConnections.remove(windowId); return windowId; @@ -729,9 +748,9 @@ public void setPictureInPictureActionReplacingConnection( mPictureInPictureActionReplacingConnection = null; } if (connection != null) { - AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper( + RemoteAccessibilityConnection wrapper = new RemoteAccessibilityConnection( AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID, - connection, UserHandle.USER_ALL); + connection, "foo.bar.baz", Process.SYSTEM_UID, UserHandle.USER_ALL); mPictureInPictureActionReplacingConnection = wrapper; wrapper.linkToDeath(); } @@ -2023,7 +2042,8 @@ private void updateAccessibilityShortcutLocked(UserState userState) { final long identity = Binder.clearCallingIdentity(); try { Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null, userState.mUserId); + Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null, + userState.mUserId); Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 0, userState.mUserId); @@ -2354,18 +2374,35 @@ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { } } - private class AccessibilityConnectionWrapper implements DeathRecipient { + class RemoteAccessibilityConnection implements DeathRecipient { + private final int mUid; + private final String mPackageName; private final int mWindowId; private final int mUserId; private final IAccessibilityInteractionConnection mConnection; - public AccessibilityConnectionWrapper(int windowId, - IAccessibilityInteractionConnection connection, int userId) { + RemoteAccessibilityConnection(int windowId, + IAccessibilityInteractionConnection connection, + String packageName, int uid, int userId) { mWindowId = windowId; + mPackageName = packageName; + mUid = uid; mUserId = userId; mConnection = connection; } + public int getUid() { + return mUid; + } + + public String getPackageName() { + return mPackageName; + } + + public IAccessibilityInteractionConnection getRemote() { + return mConnection; + } + public void linkToDeath() throws RemoteException { mConnection.asBinder().linkToDeath(this, 0); } @@ -3041,28 +3078,28 @@ public AccessibilityWindowInfo getWindow(int windowId) { } @Override - public boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId, + public String[] findAccessibilityNodeInfosByViewId(int accessibilityWindowId, long accessibilityNodeId, String viewIdResName, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; + RemoteAccessibilityConnection connection = null; Region partialInteractiveRegion = Region.obtain(); MagnificationSpec spec; synchronized (mLock) { mUsesAccessibilityCache = true; if (!isCalledForCurrentUserLocked()) { - return false; + return null; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { - return false; + return null; } else { connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - return false; + return null; } } if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( @@ -3075,12 +3112,14 @@ public boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId, final int interrogatingPid = Binder.getCallingPid(); callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); + final int callingUid = Binder.getCallingUid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfosByViewId(accessibilityNodeId, viewIdResName, - partialInteractiveRegion, interactionId, callback, mFetchFlags, - interrogatingPid, interrogatingTid, spec); - return true; + connection.getRemote().findAccessibilityNodeInfosByViewId(accessibilityNodeId, + viewIdResName, partialInteractiveRegion, interactionId, callback, + mFetchFlags, interrogatingPid, interrogatingTid, spec); + return mSecurityPolicy.computeValidReportedPackages(callingUid, + connection.getPackageName(), connection.getUid()); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId()."); @@ -3088,36 +3127,36 @@ public boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId, } finally { Binder.restoreCallingIdentity(identityToken); // Recycle if passed to another process. - if (partialInteractiveRegion != null && Binder.isProxy(connection)) { + if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) { partialInteractiveRegion.recycle(); } } - return false; + return null; } @Override - public boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, + public String[] findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; + RemoteAccessibilityConnection connection = null; Region partialInteractiveRegion = Region.obtain(); MagnificationSpec spec; synchronized (mLock) { mUsesAccessibilityCache = true; if (!isCalledForCurrentUserLocked()) { - return false; + return null; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { - return false; + return null; } else { connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - return false; + return null; } } if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( @@ -3130,12 +3169,14 @@ public boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, final int interrogatingPid = Binder.getCallingPid(); callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); + final int callingUid = Binder.getCallingUid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text, + connection.getRemote().findAccessibilityNodeInfosByText(accessibilityNodeId, text, partialInteractiveRegion, interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, spec); - return true; + return mSecurityPolicy.computeValidReportedPackages(callingUid, + connection.getPackageName(), connection.getUid()); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()"); @@ -3143,36 +3184,36 @@ public boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, } finally { Binder.restoreCallingIdentity(identityToken); // Recycle if passed to another process. - if (partialInteractiveRegion != null && Binder.isProxy(connection)) { + if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) { partialInteractiveRegion.recycle(); } } - return false; + return null; } @Override - public boolean findAccessibilityNodeInfoByAccessibilityId( + public String[] findAccessibilityNodeInfoByAccessibilityId( int accessibilityWindowId, long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, long interrogatingTid, Bundle arguments) throws RemoteException { final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; + RemoteAccessibilityConnection connection = null; Region partialInteractiveRegion = Region.obtain(); MagnificationSpec spec; synchronized (mLock) { mUsesAccessibilityCache = true; if (!isCalledForCurrentUserLocked()) { - return false; + return null; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { - return false; + return null; } else { connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - return false; + return null; } } if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( @@ -3185,12 +3226,14 @@ public boolean findAccessibilityNodeInfoByAccessibilityId( final int interrogatingPid = Binder.getCallingPid(); callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); + final int callingUid = Binder.getCallingUid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId, - partialInteractiveRegion, interactionId, callback, mFetchFlags | flags, - interrogatingPid, interrogatingTid, spec, arguments); - return true; + connection.getRemote().findAccessibilityNodeInfoByAccessibilityId( + accessibilityNodeId, partialInteractiveRegion, interactionId, callback, + mFetchFlags | flags, interrogatingPid, interrogatingTid, spec, arguments); + return mSecurityPolicy.computeValidReportedPackages(callingUid, + connection.getPackageName(), connection.getUid()); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()"); @@ -3198,36 +3241,36 @@ public boolean findAccessibilityNodeInfoByAccessibilityId( } finally { Binder.restoreCallingIdentity(identityToken); // Recycle if passed to another process. - if (partialInteractiveRegion != null && Binder.isProxy(connection)) { + if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) { partialInteractiveRegion.recycle(); } } - return false; + return null; } @Override - public boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, + public String[] findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; + RemoteAccessibilityConnection connection = null; Region partialInteractiveRegion = Region.obtain(); MagnificationSpec spec; synchronized (mLock) { if (!isCalledForCurrentUserLocked()) { - return false; + return null; } resolvedWindowId = resolveAccessibilityWindowIdForFindFocusLocked( accessibilityWindowId, focusType); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { - return false; + return null; } else { connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - return false; + return null; } } if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( @@ -3240,12 +3283,14 @@ public boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, final int interrogatingPid = Binder.getCallingPid(); callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); + final int callingUid = Binder.getCallingUid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findFocus(accessibilityNodeId, focusType, partialInteractiveRegion, - interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, - spec); - return true; + connection.getRemote().findFocus(accessibilityNodeId, focusType, + partialInteractiveRegion, interactionId, callback, mFetchFlags, + interrogatingPid, interrogatingTid, spec); + return mSecurityPolicy.computeValidReportedPackages(callingUid, + connection.getPackageName(), connection.getUid()); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling findFocus()"); @@ -3253,35 +3298,35 @@ public boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, } finally { Binder.restoreCallingIdentity(identityToken); // Recycle if passed to another process. - if (partialInteractiveRegion != null && Binder.isProxy(connection)) { + if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) { partialInteractiveRegion.recycle(); } } - return false; + return null; } @Override - public boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, + public String[] focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; + RemoteAccessibilityConnection connection = null; Region partialInteractiveRegion = Region.obtain(); MagnificationSpec spec; synchronized (mLock) { if (!isCalledForCurrentUserLocked()) { - return false; + return null; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { - return false; + return null; } else { connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - return false; + return null; } } if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( @@ -3294,12 +3339,14 @@ public boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, final int interrogatingPid = Binder.getCallingPid(); callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); + final int callingUid = Binder.getCallingUid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.focusSearch(accessibilityNodeId, direction, partialInteractiveRegion, - interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, - spec); - return true; + connection.getRemote().focusSearch(accessibilityNodeId, direction, + partialInteractiveRegion, interactionId, callback, mFetchFlags, + interrogatingPid, interrogatingTid, spec); + return mSecurityPolicy.computeValidReportedPackages(callingUid, + connection.getPackageName(), connection.getUid()); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling accessibilityFocusSearch()"); @@ -3307,11 +3354,11 @@ public boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, } finally { Binder.restoreCallingIdentity(identityToken); // Recycle if passed to another process. - if (partialInteractiveRegion != null && Binder.isProxy(connection)) { + if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) { partialInteractiveRegion.recycle(); } } - return false; + return null; } @Override @@ -3351,8 +3398,8 @@ public boolean performAccessibilityAction(int accessibilityWindowId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; IBinder activityToken = null; + RemoteAccessibilityConnection connection; synchronized (mLock) { if (!isCalledForCurrentUserLocked()) { return false; @@ -3375,7 +3422,7 @@ public boolean performAccessibilityAction(int accessibilityWindowId, if ((a11yWindowInfo != null) && a11yWindowInfo.inPictureInPicture()) { if ((mPictureInPictureActionReplacingConnection != null) && !isA11yFocusAction) { - connection = mPictureInPictureActionReplacingConnection.mConnection; + connection = mPictureInPictureActionReplacingConnection; } } } @@ -3391,8 +3438,9 @@ public boolean performAccessibilityAction(int accessibilityWindowId, LocalServices.getService(ActivityManagerInternal.class) .setFocusedActivity(activityToken); } - connection.performAccessibilityAction(accessibilityNodeId, action, arguments, - interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid); + connection.mConnection.performAccessibilityAction(accessibilityNodeId, action, + arguments, interactionId, callback, mFetchFlags, interrogatingPid, + interrogatingTid); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling performAccessibilityAction()"); @@ -4072,16 +4120,16 @@ private void toggleSplitScreen() { LocalServices.getService(StatusBarManagerInternal.class).toggleSplitScreen(); } - private IAccessibilityInteractionConnection getConnectionLocked(int windowId) { + private RemoteAccessibilityConnection getConnectionLocked(int windowId) { if (DEBUG) { Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId); } - AccessibilityConnectionWrapper wrapper = mGlobalInteractionConnections.get(windowId); + RemoteAccessibilityConnection wrapper = mGlobalInteractionConnections.get(windowId); if (wrapper == null) { wrapper = getCurrentUserStateLocked().mInteractionConnections.get(windowId); } if (wrapper != null && wrapper.mConnection != null) { - return wrapper.mConnection; + return wrapper; } if (DEBUG) { Slog.e(LOG_TAG, "No interaction connection to window: " + windowId); @@ -4230,6 +4278,16 @@ public void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available } } + private AppWidgetManagerInternal getAppWidgetManager() { + synchronized (mLock) { + if (mAppWidgetService == null + && mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) { + mAppWidgetService = LocalServices.getService(AppWidgetManagerInternal.class); + } + return mAppWidgetService; + } + } + final class WindowsForAccessibilityCallback implements WindowManagerInternal.WindowsForAccessibilityCallback { @@ -4507,6 +4565,78 @@ private boolean canDispatchAccessibilityEventLocked(AccessibilityEvent event) { } } + private boolean isValidPackageForUid(String packageName, int uid) { + try { + return uid == mPackageManager.getPackageUid( + packageName, UserHandle.getUserId(uid)); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + String resolveValidReportedPackageLocked(CharSequence packageName, int appId, int userId) { + // Okay to pass no package + if (packageName == null) { + return null; + } + // The system gets to pass any package + if (appId == Process.SYSTEM_UID) { + return packageName.toString(); + } + // Passing a package in your UID is fine + final String packageNameStr = packageName.toString(); + final int resolvedUid = UserHandle.getUid(userId, appId); + if (isValidPackageForUid(packageNameStr, resolvedUid)) { + return packageName.toString(); + } + // Appwidget hosts get to pass packages for widgets they host + final AppWidgetManagerInternal appWidgetManager = getAppWidgetManager(); + if (appWidgetManager != null && ArrayUtils.contains(appWidgetManager + .getHostedWidgetPackages(resolvedUid), packageNameStr)) { + return packageName.toString(); + } + // Otherwise, set the package to the first one in the UID + final String[] packageNames = mPackageManager.getPackagesForUid(resolvedUid); + if (ArrayUtils.isEmpty(packageNames)) { + return null; + } + // Okay, the caller reported a package it does not have access to. + // Instead of crashing the caller for better backwards compatibility + // we report the first package in the UID. Since most of the time apps + // don't use shared user id, this will yield correct results and for + // the edge case of using a shared user id we may report the wrong + // package but this is fine since first, this is a cheating app and + // second there is no way to get the correct package anyway. + return packageNames[0]; + } + + String[] computeValidReportedPackages(int callingUid, + String targetPackage, int targetUid) { + if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) { + // Empty array means any package is Okay + return EmptyArray.STRING; + } + // IMPORTANT: The target package is already vetted to be in the target UID + String[] uidPackages = new String[]{targetPackage}; + // Appwidget hosts get to pass packages for widgets they host + final AppWidgetManagerInternal appWidgetManager = getAppWidgetManager(); + if (appWidgetManager != null) { + final ArraySet widgetPackages = appWidgetManager + .getHostedWidgetPackages(targetUid); + if (widgetPackages != null && !widgetPackages.isEmpty()) { + final String[] validPackages = new String[uidPackages.length + + widgetPackages.size()]; + System.arraycopy(uidPackages, 0, validPackages, 0, uidPackages.length); + final int widgetPackageCount = widgetPackages.size(); + for (int i = 0; i < widgetPackageCount; i++) { + validPackages[uidPackages.length + i] = widgetPackages.valueAt(i); + } + return validPackages; + } + } + return uidPackages; + } + public void clearWindowsLocked() { List windows = Collections.emptyList(); final int activeWindowId = mActiveWindowId; @@ -4932,7 +5062,7 @@ private class UserState { public final RemoteCallbackList mUserClients = new RemoteCallbackList<>(); - public final SparseArray mInteractionConnections = + public final SparseArray mInteractionConnections = new SparseArray<>(); public final SparseArray mWindowTokens = new SparseArray<>(); diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 80b54770e4b72..a57010b270379 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -29,6 +29,7 @@ import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DevicePolicyManagerInternal.OnCrossProfileWidgetProvidersChangeListener; import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetManagerInternal; import android.appwidget.AppWidgetProviderInfo; import android.appwidget.PendingHostUpdate; import android.content.BroadcastReceiver; @@ -99,6 +100,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.widget.IRemoteViewsAdapterConnection; import com.android.internal.widget.IRemoteViewsFactory; @@ -107,6 +109,7 @@ import com.android.server.policy.IconUtilities; import libcore.io.IoUtils; +import libcore.util.EmptyArray; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -256,6 +259,8 @@ public void onStart() { computeMaximumWidgetBitmapMemory(); registerBroadcastReceiver(); registerOnCrossProfileProvidersChangedListener(); + + LocalServices.addService(AppWidgetManagerInternal.class, new AppWidgetManagerLocal()); } private void computeMaximumWidgetBitmapMemory() { @@ -4709,4 +4714,24 @@ public RestoreUpdateRecord(int theOldId, int theNewId) { } } } + + private class AppWidgetManagerLocal extends AppWidgetManagerInternal { + @Override + public ArraySet getHostedWidgetPackages(int uid) { + synchronized (mLock) { + ArraySet widgetPackages = null; + final int widgetCount = mWidgets.size(); + for (int i = 0; i < widgetCount; i++) { + final Widget widget = mWidgets.get(i); + if (widget.host.id.uid == uid) { + if (widgetPackages == null) { + widgetPackages = new ArraySet<>(); + } + widgetPackages.add(widget.provider.id.componentName.getPackageName()); + } + } + return widgetPackages; + } + } + } } From 17552c35be5698c852884b326f0bfc35122572d3 Mon Sep 17 00:00:00 2001 From: y Date: Thu, 5 Apr 2018 17:57:27 -0700 Subject: [PATCH 05/73] ResStringPool: Fix security vulnerability Adds detection of attacker-modified size and data fields passed to ResStringPool::setTo(). These attacks are modified apks that AAPT would not normally generate. In the rare case this occurs, the installation cannot be allowed to continue. Bug: 71361168 Bug: 71360999 Test: run cts -m CtsAppSecurityHostTestCases \ -t android.appsecurity.cts.CorruptApkTests Change-Id: If7eb93a9e723b16c8a0556fc4e20006aa0391d57 Merged-In: If7eb93a9e723b16c8a0556fc4e20006aa0391d57 (cherry picked from commit 7e54c3f261d81316b75cb734075319108d8bc1d1) --- libs/androidfw/ResourceTypes.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 733ffb180b3b3..401c7b070188c 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -457,6 +457,22 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) uninit(); + // The chunk must be at least the size of the string pool header. + if (size < sizeof(ResStringPool_header)) { + LOG_ALWAYS_FATAL("Bad string block: data size %zu is too small to be a string block", size); + return (mError=BAD_TYPE); + } + + // The data is at least as big as a ResChunk_header, so we can safely validate the other + // header fields. + // `data + size` is safe because the source of `size` comes from the kernel/filesystem. + if (validate_chunk(reinterpret_cast(data), sizeof(ResStringPool_header), + reinterpret_cast(data) + size, + "ResStringPool_header") != NO_ERROR) { + LOG_ALWAYS_FATAL("Bad string block: malformed block dimensions"); + return (mError=BAD_TYPE); + } + const bool notDeviceEndian = htods(0xf0) != 0xf0; if (copyData || notDeviceEndian) { @@ -468,6 +484,8 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) data = mOwnedData; } + // The size has been checked, so it is safe to read the data in the ResStringPool_header + // data structure. mHeader = (const ResStringPool_header*)data; if (notDeviceEndian) { From ebf8e3cac7dffa444e3180daba1c6e84ae37dd98 Mon Sep 17 00:00:00 2001 From: "Philip P. Moltmann" Date: Thu, 12 Apr 2018 14:45:14 -0700 Subject: [PATCH 06/73] DO NOT MERGE (O) Revoke permision when group changed If a run time permission of a group is already granted we grant the other permission of the group automatically when requested. Hence if an already granted permission changed its group during an update suddenly permission of a potentially not approved group will get auto-granted. This is undesirable, hence we revoke the permission during the update process. Test: atest android.permission.cts.PermissionGroupChange Change-Id: Ib2165d1ae53b80455ebe02e07775853e37a2e339 Fixes: 72710897 (cherry picked from commit 0ed1b472af194896fcf421dc862c443343c7c127) --- .../server/pm/PackageManagerService.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 06600bf75ffad..d6b572835950b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -184,6 +184,7 @@ import android.graphics.Bitmap; import android.hardware.display.DisplayManager; import android.net.Uri; +import android.os.AsyncTask; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -5849,6 +5850,83 @@ private void revokeRuntimePermission(String packageName, String name, int userId killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED); } + /** + * We might auto-grant permissions if any permission of the group is already granted. Hence if + * the group of a granted permission changes we need to revoke it to avoid having permissions of + * the new group auto-granted. + * + * @param newPackage The new package that was installed + * @param oldPackage The old package that was updated + * @param allPackageNames All package names + */ + private void revokeRuntimePermissionsIfGroupChanged( + PackageParser.Package newPackage, + PackageParser.Package oldPackage, + ArrayList allPackageNames) { + final int numOldPackagePermissions = oldPackage.permissions.size(); + final ArrayMap oldPermissionNameToGroupName + = new ArrayMap<>(numOldPackagePermissions); + + for (int i = 0; i < numOldPackagePermissions; i++) { + final PackageParser.Permission permission = oldPackage.permissions.get(i); + + if (permission.group != null) { + oldPermissionNameToGroupName.put(permission.info.name, + permission.group.info.name); + } + } + + final int numNewPackagePermissions = newPackage.permissions.size(); + for (int newPermissionNum = 0; newPermissionNum < numNewPackagePermissions; + newPermissionNum++) { + final PackageParser.Permission newPermission = + newPackage.permissions.get(newPermissionNum); + final int newProtection = newPermission.info.protectionLevel; + + if ((newProtection & PermissionInfo.PROTECTION_DANGEROUS) != 0) { + final String permissionName = newPermission.info.name; + final String newPermissionGroupName = + newPermission.group == null ? null : newPermission.group.info.name; + final String oldPermissionGroupName = oldPermissionNameToGroupName.get( + permissionName); + + if (newPermissionGroupName != null + && !newPermissionGroupName.equals(oldPermissionGroupName)) { + final List users = mContext.getSystemService(UserManager.class) + .getUsers(); + + final int numUsers = users.size(); + for (int userNum = 0; userNum < numUsers; userNum++) { + final int userId = users.get(userNum).id; + final int numPackages = allPackageNames.size(); + + for (int packageNum = 0; packageNum < numPackages; packageNum++) { + final String packageName = allPackageNames.get(packageNum); + + if (checkPermission(permissionName, packageName, userId) + == PackageManager.PERMISSION_GRANTED) { + EventLog.writeEvent(0x534e4554, "72710897", + newPackage.applicationInfo.uid, + "Revoking permission", permissionName, "from package", + packageName, "as the group changed from", + oldPermissionGroupName, "to", newPermissionGroupName); + + try { + revokeRuntimePermission(packageName, permissionName, userId, + false); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Could not revoke " + permissionName + " from " + + packageName, e); + } + } + } + } + } + } + } + } + + /** * Get the first event id for the permission. * @@ -10743,6 +10821,8 @@ private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, String primaryCpuAbiFromSettings = null; String secondaryCpuAbiFromSettings = null; + final PackageParser.Package oldPkg; + // writer synchronized (mPackages) { if (pkg.mSharedUserId != null) { @@ -10843,6 +10923,12 @@ private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, final PackageSetting disabledPkgSetting = mSettings.getDisabledSystemPkgLPr(pkg.packageName); + if (oldPkgSetting == null) { + oldPkg = null; + } else { + oldPkg = oldPkgSetting.pkg; + } + String[] usesStaticLibraries = null; if (pkg.usesStaticLibraries != null) { usesStaticLibraries = new String[pkg.usesStaticLibraries.size()]; @@ -11175,6 +11261,25 @@ private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.appId); } } + + if (oldPkg != null) { + // We need to call revokeRuntimePermissionsIfGroupChanged async as permission + // revokation from this method might need to kill apps which need the + // mPackages lock on a different thread. This would dead lock. + // + // Hence create a copy of all package names and pass it into + // revokeRuntimePermissionsIfGroupChanged. Only for those permissions might get + // revoked. If a new package is added before the async code runs the permission + // won't be granted yet, hence new packages are no problem. + final ArrayList allPackageNames = new ArrayList<>(mPackages.keySet()); + + AsyncTask.execute(new Runnable() { + public void run() { + revokeRuntimePermissionsIfGroupChanged(pkg, oldPkg, allPackageNames); + } + }); + } + return pkg; } From 11c26981eed3ffd627ee842619878ee2ac1c1ed4 Mon Sep 17 00:00:00 2001 From: sqian Date: Fri, 13 Apr 2018 17:10:05 -0700 Subject: [PATCH 07/73] Fix broken check for TelephonyManager#getForbiddenPlmns (backport from a fix merged in pi-dev) Bug: 73884967 Test: Treehugger Change-Id: I9deaae20893184cde36dcd936fe83708fa60b830 Merged-In: I0cf7920e138892fbcab71fae0eed1293f0b2e404 Merged-In: I9e3456e5f1e479b0e2b102f6c90db57cd0e977fe (cherry picked from commit 7b52a48d6b10e3ed2806b57d39a2d9211dd4b585) --- telephony/java/android/telephony/TelephonyManager.java | 2 +- telephony/java/com/android/internal/telephony/ITelephony.aidl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index c9afb9f9ea314..d4a8066213622 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -4605,7 +4605,7 @@ public String[] getForbiddenPlmns(int subId, int appType) { ITelephony telephony = getITelephony(); if (telephony == null) return null; - return telephony.getForbiddenPlmns(subId, appType); + return telephony.getForbiddenPlmns(subId, appType, mContext.getOpPackageName()); } catch (RemoteException ex) { return null; } catch (NullPointerException ex) { diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 9262ec5ed53bf..235b5eecb986b 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1352,12 +1352,12 @@ interface ITelephony { * Returns null if the query fails. * * - *

Requires that the calling app has READ_PRIVILEGED_PHONE_STATE + *

Requires that the calling app has READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE * * @param subId subscription ID used for authentication * @param appType the icc application type, like {@link #APPTYPE_USIM} */ - String[] getForbiddenPlmns(int subId, int appType); + String[] getForbiddenPlmns(int subId, int appType, String callingPackage); /** * Check if phone is in emergency callback mode From 86ac001419f140ed849eec83e1c42eb1c75860a7 Mon Sep 17 00:00:00 2001 From: Hansong Zhang Date: Thu, 26 Apr 2018 14:22:39 -0700 Subject: [PATCH 08/73] DO NOT MERGE Truncate newline and tab characters in BluetoothDevice name Test: manual Bug: 73173182 Change-Id: I7f2201cab36adf7f01d1a794d783cb78a536811f (cherry picked from commit 24da173b63b17a0bc6c80b2fcfefa7fe4574a15b) --- core/java/android/bluetooth/BluetoothDevice.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 98cd319a399a7..9498f125c2a6c 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -808,7 +808,11 @@ public String getName() { return null; } try { - return service.getRemoteName(this); + String name = service.getRemoteName(this); + if (name != null) { + return name.replaceAll("[\\t\\n\\r]+", " "); + } + return null; } catch (RemoteException e) { Log.e(TAG, "", e); } From 1449bd2e4165a6c7c86dd1b0ca85950fe469ea3d Mon Sep 17 00:00:00 2001 From: Ecco Park Date: Thu, 3 May 2018 14:49:40 -0700 Subject: [PATCH 09/73] Osu: fixed Mismatch between createFromParcel and writeToParcel Bug: 77600924 Change-Id: I46d765892e8e6839ed5140a3b0d6bb1815ccf9bc Signed-off-by: Ecco Park (cherry picked from commit 9a59cf84506e9fa841524ac2c70ae683449e709a) --- .../Osu/src/com/android/hotspot2/flow/OSUInfo.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/Osu/src/com/android/hotspot2/flow/OSUInfo.java b/packages/Osu/src/com/android/hotspot2/flow/OSUInfo.java index 401eccb96d050..ae47ddd22fc38 100644 --- a/packages/Osu/src/com/android/hotspot2/flow/OSUInfo.java +++ b/packages/Osu/src/com/android/hotspot2/flow/OSUInfo.java @@ -42,6 +42,7 @@ public enum IconStatus { private HSIconFileElement mIconFileElement; private String mIconFileName; private IconInfo mIconInfo; + private int mIconIndex; public OSUInfo(ScanResult scanResult, OSUProvider osuProvider, int osuID) { mOsuID = osuID; @@ -50,6 +51,7 @@ public OSUInfo(ScanResult scanResult, OSUProvider osuProvider, int osuID) { mAnqpDomID = scanResult.anqpDomainId; mAdvertisingSSID = scanResult.SSID; mOSUProvider = osuProvider; + mIconIndex = -1; } public long getOSUBssid() { @@ -157,12 +159,15 @@ public String getIconFileName() { public void setIconFileElement(HSIconFileElement iconFileElement, String fileName) { synchronized (mOSUProvider) { mIconFileElement = iconFileElement; + int index = 0; for (IconInfo iconInfo : mOSUProvider.getIcons()) { if (iconInfo.getFileName().equals(fileName)) { mIconInfo = iconInfo; mIconFileName = fileName; + mIconIndex = index; break; } + index++; } mIconStatus = IconStatus.Available; } @@ -285,9 +290,9 @@ private OSUInfo(Parcel in) { return; } mIconFileElement = new HSIconFileElement(in); - int iconIndex = in.readInt(); - mIconInfo = iconIndex >= 0 && iconIndex < mOSUProvider.getIcons().size() - ? mOSUProvider.getIcons().get(iconIndex) : null; + mIconIndex = in.readInt(); + mIconInfo = mIconIndex >= 0 && mIconIndex < mOSUProvider.getIcons().size() + ? mOSUProvider.getIcons().get(mIconIndex) : null; } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @@ -317,5 +322,6 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mIconStatus.ordinal()); mOSUProvider.writeParcel(dest); mIconFileElement.writeParcel(dest); + dest.writeInt(mIconIndex); } } From 959db062fb72575322b3349170a2b523049e1d07 Mon Sep 17 00:00:00 2001 From: Eugene Susla Date: Mon, 11 Dec 2017 10:07:03 -0800 Subject: [PATCH 10/73] Nullcheck to fix Autofill CTS Test: presubmit Fixes: 70506475 Bug: 69981755 Change-Id: I187bed4889a4901a7137a2995178ea651ed09186 (cherry picked from commit 6c68a692880b7cc981e130aace0edb9f2fcffff6) --- .../AccessibilityInteractionClient.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index be3b34d0ccf8a..980a2c926c1a9 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -734,11 +734,14 @@ private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, if (info != null) { info.setConnectionId(connectionId); // Empty array means any package name is Okay - if (!ArrayUtils.isEmpty(packageNames) - && !ArrayUtils.contains(packageNames, info.getPackageName().toString())) { - // If the node package not one of the valid ones, pick the top one - this - // is one of the packages running in the introspected UID. - info.setPackageName(packageNames[0]); + if (!ArrayUtils.isEmpty(packageNames)) { + CharSequence packageName = info.getPackageName(); + if (packageName == null + || !ArrayUtils.contains(packageNames, packageName.toString())) { + // If the node package not one of the valid ones, pick the top one - this + // is one of the packages running in the introspected UID. + info.setPackageName(packageNames[0]); + } } info.setSealed(true); if (!bypassCache) { From 51f7f6bd1f064450bf5e8bc6e0fe74f44927ae38 Mon Sep 17 00:00:00 2001 From: Tony Mak Date: Thu, 14 Dec 2017 12:40:07 +0000 Subject: [PATCH 11/73] clearCallingIdentity before calling into getPackageUidAsUser Fix: 70585244 Bug: 69981755 Test: Enable any accessibility service -> inflate work profile -> Tap on any work app -> no longer crash Test: cts-tradefed run cts-dev --module DevicePolicyManager --test com.android.cts.devicepolicy.CrossProfileAppsHostSideTest.testPrimaryUserToManagedProfile Change-Id: I80d18f4e2ab76a228cb0aa2c8312c323a9b5c84d (cherry picked from commit 857326e3731939f6ec7979e1d86585bf0ea484f4) --- .../server/accessibility/AccessibilityManagerService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index d0d65d94fb5b6..8b4076af7759a 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -4566,11 +4566,14 @@ private boolean canDispatchAccessibilityEventLocked(AccessibilityEvent event) { } private boolean isValidPackageForUid(String packageName, int uid) { + final long token = Binder.clearCallingIdentity(); try { return uid == mPackageManager.getPackageUid( packageName, UserHandle.getUserId(uid)); } catch (PackageManager.NameNotFoundException e) { return false; + } finally { + Binder.restoreCallingIdentity(token); } } From df55e0aef345d86e51a2da6b69a1b1f64eea2ad4 Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Thu, 3 May 2018 21:07:58 -0700 Subject: [PATCH 12/73] DO NOT MERGE: Fix ConnectivityController meteredness checks This patch corrects ConnectivityController's meteredness checks to perform correct meteredness checks while VPNs are running. This fixes a bug in O-MR1 where any apps using the DownloadProvider with unmetered network constraints fail to start while the VPN is enabled. This change adds a bespoke method for ConnectivityController, allowing it to correctly identify the meteredness without affecting public API surfaces. Bug: 78644887 Test: Built, flashed on Walleye, and tested. Test: Additional test coverage in subsequent patch(es). Change-Id: Ie1d11d93d51d936ce81cd5984af61bde30325983 (cherry picked from commit d08ab5a641d9d81314c9439724dd34338fa81d58) --- .../java/android/net/ConnectivityManager.java | 22 +++++++++++++++++++ .../android/net/IConnectivityManager.aidl | 1 + .../android/server/ConnectivityService.java | 12 +++++++++- .../controllers/ConnectivityController.java | 6 ++--- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index d7ecc81ffdba1..467eb9b0b0bfc 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -2504,6 +2504,28 @@ public boolean isActiveNetworkMetered() { } } + /** + * Returns if the active data network for the given UID is metered. A network + * is classified as metered when the user is sensitive to heavy data usage on + * that connection due to monetary costs, data limitations or + * battery/performance issues. You should check this before doing large + * data transfers, and warn the user or delay the operation until another + * network is available. + * + * @return {@code true} if large transfers should be avoided, otherwise + * {@code false}. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) + public boolean isActiveNetworkMeteredForUid(int uid) { + try { + return mService.isActiveNetworkMeteredForUid(uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * If the LockdownVpn mechanism is enabled, updates the vpn * with a reload of its profile. diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index a6fe7389bc723..f11372c2b31cb 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -66,6 +66,7 @@ interface IConnectivityManager NetworkQuotaInfo getActiveNetworkQuotaInfo(); boolean isActiveNetworkMetered(); + boolean isActiveNetworkMeteredForUid(int uid); boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index c1801b80af0d9..c2b83d98a8dfe 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -1339,7 +1339,17 @@ public NetworkQuotaInfo getActiveNetworkQuotaInfo() { public boolean isActiveNetworkMetered() { enforceAccessPermission(); - final int uid = Binder.getCallingUid(); + return isActiveNetworkMeteredCommon(Binder.getCallingUid()); + } + + @Override + public boolean isActiveNetworkMeteredForUid(int uid) { + enforceConnectivityInternalPermission(); + + return isActiveNetworkMeteredCommon(uid); + } + + private boolean isActiveNetworkMeteredCommon(int uid) { final NetworkCapabilities caps = getUnfilteredActiveNetworkState(uid).networkCapabilities; if (caps != null) { return !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java index 78367fe97a540..4d5a920dd54f5 100644 --- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java +++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java @@ -112,10 +112,8 @@ private boolean updateConstraintsSatisfied(JobStatus jobStatus) { final boolean connected = (info != null) && info.isConnected(); final boolean connectionUsable = connected && validated; - final boolean metered = connected && (capabilities != null) - && !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); - final boolean unmetered = connected && (capabilities != null) - && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + final boolean metered = connected && mConnManager.isActiveNetworkMeteredForUid(jobUid); + final boolean unmetered = connected && !mConnManager.isActiveNetworkMeteredForUid(jobUid); final boolean notRoaming = connected && (info != null) && !info.isRoaming(); From a3ae24c876d14bf3566af41dc7d6ce07e088b89d Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Tue, 20 Feb 2018 15:19:59 -0800 Subject: [PATCH 13/73] DO NOT MERGE: Add unit tests to ensure VPN meteredness These new tests ensure that VPNs report the meteredness of their underlying networks correctly. The added test verifies VPN meteredness for cases of metered and unmetered WiFi and Cell Bug: 78644887 Test: This; ran on walleye-eng Change-Id: I28bdc71a336bfd97f7908455d4781d774df44b87 (cherry picked from commit 66bc52884b1009fca7917ae89e72e8aa40f394d1) --- .../android/server/ConnectivityService.java | 58 ++++++++----- .../server/ConnectivityServiceTest.java | 87 +++++++++++++++++++ 2 files changed, 124 insertions(+), 21 deletions(-) diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index c2b83d98a8dfe..6e8c0d4a55c17 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -969,7 +969,7 @@ private Network[] getVpnUnderlyingNetworks(int uid) { if (!mLockdownEnabled) { int user = UserHandle.getUserId(uid); synchronized (mVpns) { - Vpn vpn = mVpns.get(user); + Vpn vpn = getVpn(user); if (vpn != null && vpn.appliesToUid(uid)) { return vpn.getUnderlyingNetworks(); } @@ -1017,7 +1017,7 @@ private boolean isNetworkWithLinkPropertiesBlocked(LinkProperties lp, int uid, return false; } synchronized (mVpns) { - final Vpn vpn = mVpns.get(UserHandle.getUserId(uid)); + final Vpn vpn = getVpn(UserHandle.getUserId(uid)); if (vpn != null && vpn.isBlockingUid(uid)) { return true; } @@ -1094,7 +1094,7 @@ private Network getActiveNetworkForUidInternal(final int uid, boolean ignoreBloc final int user = UserHandle.getUserId(uid); int vpnNetId = NETID_UNSET; synchronized (mVpns) { - final Vpn vpn = mVpns.get(user); + final Vpn vpn = getVpn(user); if (vpn != null && vpn.appliesToUid(uid)) vpnNetId = vpn.getNetId(); } NetworkAgentInfo nai; @@ -1224,7 +1224,7 @@ public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) { if (!mLockdownEnabled) { synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn != null) { Network[] networks = vpn.getUnderlyingNetworks(); if (networks != null) { @@ -3424,7 +3424,7 @@ public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPacka throwIfLockdownEnabled(); synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn != null) { return vpn.prepare(oldPackage, newPackage); } else { @@ -3451,7 +3451,7 @@ public void setVpnPackageAuthorization(String packageName, int userId, boolean a enforceCrossUserPermission(userId); synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn != null) { vpn.setPackageAuthorization(packageName, authorized); } @@ -3470,7 +3470,7 @@ public ParcelFileDescriptor establishVpn(VpnConfig config) { throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { - return mVpns.get(user).establish(config); + return getVpn(user).establish(config); } } @@ -3487,7 +3487,7 @@ public void startLegacyVpn(VpnProfile profile) { } int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { - mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress); + getVpn(user).startLegacyVpn(profile, mKeyStore, egress); } } @@ -3501,7 +3501,7 @@ public LegacyVpnInfo getLegacyVpnInfo(int userId) { enforceCrossUserPermission(userId); synchronized (mVpns) { - return mVpns.get(userId).getLegacyVpnInfo(); + return getVpn(userId).getLegacyVpnInfo(); } } @@ -3565,7 +3565,7 @@ private VpnInfo createVpnInfo(Vpn vpn) { public VpnConfig getVpnConfig(int userId) { enforceCrossUserPermission(userId); synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn != null) { return vpn.getVpnConfig(); } else { @@ -3599,7 +3599,7 @@ public boolean updateLockdownVpn() { } int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { - Vpn vpn = mVpns.get(user); + Vpn vpn = getVpn(user); if (vpn == null) { Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown"); return false; @@ -3646,7 +3646,7 @@ private void throwIfLockdownEnabled() { */ private boolean startAlwaysOnVpn(int userId) { synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn == null) { // Shouldn't happen as all codepaths that point here should have checked the Vpn // exists already. @@ -3664,7 +3664,7 @@ public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) { enforceCrossUserPermission(userId); synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn == null) { Slog.w(TAG, "User " + userId + " has no Vpn configuration"); return false; @@ -3684,7 +3684,7 @@ public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean loc } synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn == null) { Slog.w(TAG, "User " + userId + " has no Vpn configuration"); return false; @@ -3706,7 +3706,7 @@ public String getAlwaysOnVpnPackage(int userId) { enforceCrossUserPermission(userId); synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn == null) { Slog.w(TAG, "User " + userId + " has no Vpn configuration"); return null; @@ -3852,22 +3852,38 @@ public void setAirplaneMode(boolean enable) { private void onUserStart(int userId) { synchronized (mVpns) { - Vpn userVpn = mVpns.get(userId); + Vpn userVpn = getVpn(userId); if (userVpn != null) { loge("Starting user already has a VPN"); return; } userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId); - mVpns.put(userId, userVpn); + setVpn(userId, userVpn); } if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { updateLockdownVpn(); } } + /** @hide */ + @VisibleForTesting + Vpn getVpn(int userId) { + synchronized (mVpns) { + return mVpns.get(userId); + } + } + + /** @hide */ + @VisibleForTesting + void setVpn(int userId, Vpn userVpn) { + synchronized (mVpns) { + mVpns.put(userId, userVpn); + } + } + private void onUserStop(int userId) { synchronized (mVpns) { - Vpn userVpn = mVpns.get(userId); + Vpn userVpn = getVpn(userId); if (userVpn == null) { loge("Stopped user has no VPN"); return; @@ -5439,7 +5455,7 @@ public boolean addVpnAddress(String address, int prefixLength) { throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { - return mVpns.get(user).addAddress(address, prefixLength); + return getVpn(user).addAddress(address, prefixLength); } } @@ -5448,7 +5464,7 @@ public boolean removeVpnAddress(String address, int prefixLength) { throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { - return mVpns.get(user).removeAddress(address, prefixLength); + return getVpn(user).removeAddress(address, prefixLength); } } @@ -5458,7 +5474,7 @@ public boolean setUnderlyingNetworksForVpn(Network[] networks) { int user = UserHandle.getUserId(Binder.getCallingUid()); boolean success; synchronized (mVpns) { - success = mVpns.get(user).setUnderlyingNetworks(networks); + success = getVpn(user).setUnderlyingNetworks(networks); } if (success) { notifyIfacesChangedForNetworkStats(); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 6674f20317b3f..504a2dbc362b7 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -22,6 +22,7 @@ import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA; import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.ConnectivityManager.TYPE_VPN; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.NetworkCapabilities.*; @@ -102,6 +103,7 @@ import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkMonitor; import com.android.server.connectivity.NetworkMonitor.CaptivePortalProbeResult; +import com.android.server.connectivity.Vpn; import com.android.server.net.NetworkPinner; import com.android.server.net.NetworkPolicyManagerInternal; @@ -333,6 +335,9 @@ private class MockNetworkAgent { case TRANSPORT_WIFI_AWARE: mScore = 20; break; + case TRANSPORT_VPN: + mScore = 0; + break; default: throw new UnsupportedOperationException("unimplemented network type"); } @@ -868,6 +873,8 @@ private static int transportToLegacyType(int transport) { return TYPE_WIFI; case TRANSPORT_CELLULAR: return TYPE_MOBILE; + case TRANSPORT_VPN: + return TYPE_VPN; default: return TYPE_NONE; } @@ -3447,4 +3454,84 @@ private static void assertException(Runnable block, Class expected) { return; } } + + @SmallTest + public void testVpnNetworkMetered() { + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(callback); + + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + final TestNetworkCallback cellCallback = new TestNetworkCallback(); + mCm.registerNetworkCallback(cellRequest, cellCallback); + + // Setup cellular + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + verifyActiveNetwork(TRANSPORT_CELLULAR); + + // Verify meteredness of cellular + assertTrue(mCm.isActiveNetworkMetered()); + + // Setup Wifi + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + callback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + cellCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + verifyActiveNetwork(TRANSPORT_WIFI); + + // Verify meteredness of WiFi + assertTrue(mCm.isActiveNetworkMetered()); + + // Verify that setting unmetered on Wifi changes ActiveNetworkMetered + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + callback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent); + assertFalse(mCm.isActiveNetworkMetered()); + + // Setup VPN + final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); + vpnNetworkAgent.connect(true); + + Vpn mockVpn = mock(Vpn.class); + when(mockVpn.appliesToUid(anyInt())).thenReturn(true); + when(mockVpn.getNetId()).thenReturn(vpnNetworkAgent.getNetwork().netId); + + Vpn oldVpn = mService.getVpn(UserHandle.myUserId()); + mService.setVpn(UserHandle.myUserId(), mockVpn); + assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + // Verify meteredness of VPN on default network + when(mockVpn.getUnderlyingNetworks()).thenReturn(null); + assertFalse(mCm.isActiveNetworkMetered()); + assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + + // Verify meteredness of VPN on unmetered wifi + when(mockVpn.getUnderlyingNetworks()) + .thenReturn(new Network[] {mWiFiNetworkAgent.getNetwork()}); + assertFalse(mCm.isActiveNetworkMetered()); + assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + + // Set WiFi as metered, then check to see that it has been updated on the VPN + mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); + callback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent); + assertTrue(mCm.isActiveNetworkMetered()); + assertTrue(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + + // Switch to cellular + when(mockVpn.getUnderlyingNetworks()) + .thenReturn(new Network[] {mCellNetworkAgent.getNetwork()}); + assertTrue(mCm.isActiveNetworkMetered()); + assertTrue(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + + // Test unmetered cellular + mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + cellCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent); + assertFalse(mCm.isActiveNetworkMetered()); + assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + + mService.setVpn(UserHandle.myUserId(), oldVpn); + mCm.unregisterNetworkCallback(callback); + } } From dc7662201a9839c1b05cccd588e98d2b47c38eae Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Tue, 22 May 2018 16:56:35 +0200 Subject: [PATCH 14/73] WM: Prevent secondary display focus while keyguard is up Fixes an issue where input intended for the keyguard could end up going to a different display. To prevent this, make sure that only the default display can get focused when the keyguard is showing. Change-Id: I6463c44aedca06930d2c9bda7c45ffd93141308c Merged-In: I6463c44aedca06930d2c9bda7c45ffd93141308c Fixes: 71786287 Test: atest DisplayContentTests (cherry picked from commit 3cd5e3d9bbb3255e874b8fa27d7ed506164905dd) --- .../android/view/WindowManagerPolicy.java | 5 +++++ .../server/policy/PhoneWindowManager.java | 5 +++++ .../policy/keyguard/KeyguardStateMonitor.java | 3 +++ .../server/wm/RootWindowContainer.java | 8 ++++++++ .../server/wm/WindowManagerService.java | 13 +++++++++++++ .../server/wm/DisplayContentTests.java | 19 +++++++++++++++++++ .../server/wm/TestWindowManagerPolicy.java | 5 +++-- 7 files changed, 56 insertions(+), 2 deletions(-) diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index c4ffb4c06a26d..235e61a129f6a 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -610,6 +610,11 @@ public InputConsumer createInputConsumer(Looper looper, String name, */ void notifyKeyguardTrustedChanged(); + /** + * The keyguard showing state has changed + */ + void onKeyguardShowingAndNotOccludedChanged(); + /** * Notifies the window manager that screen is being turned off. * diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index da14c360f16ab..d3469c2346a59 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -2150,6 +2150,11 @@ public void onAppTransitionCancelledLocked(int transit) { public void onTrustedChanged() { mWindowManagerFuncs.notifyKeyguardTrustedChanged(); } + + @Override + public void onShowingChanged() { + mWindowManagerFuncs.onKeyguardShowingAndNotOccludedChanged(); + } }); } diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java index 941cd4441e23a..fd34c510d98da 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java @@ -86,6 +86,8 @@ public boolean hasLockscreenWallpaper() { @Override // Binder interface public void onShowingStateChanged(boolean showing) { mIsShowing = showing; + + mCallback.onShowingChanged(); } @Override // Binder interface @@ -119,6 +121,7 @@ public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) { public interface StateCallback { void onTrustedChanged(); + void onShowingChanged(); } public void dump(String prefix, PrintWriter pw) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index a9e56f34b0d64..5a75262a80233 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -165,10 +165,18 @@ class RootWindowContainer extends WindowContainer { } WindowState computeFocusedWindow() { + // While the keyguard is showing, we must focus anything besides the main display. + // Otherwise we risk input not going to the keyguard when the user expects it to. + final boolean forceDefaultDisplay = mService.mPolicy.isKeyguardShowingAndNotOccluded(); + for (int i = mChildren.size() - 1; i >= 0; i--) { final DisplayContent dc = mChildren.get(i); final WindowState win = dc.findFocusedWindow(); if (win != null) { + if (forceDefaultDisplay && !dc.isDefaultDisplay) { + EventLog.writeEvent(0x534e4554, "71786287", win.mOwnerUid, ""); + continue; + } return win; } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index f5cc43bd90190..d378fa3ca2294 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2928,6 +2928,11 @@ public void notifyKeyguardTrustedChanged() { mH.sendEmptyMessage(H.NOTIFY_KEYGUARD_TRUSTED_CHANGED); } + @Override + public void onKeyguardShowingAndNotOccludedChanged() { + mH.sendEmptyMessage(H.RECOMPUTE_FOCUS); + } + @Override public void screenTurningOff(ScreenOffListener listener) { mTaskSnapshotController.screenTurningOff(listener); @@ -4897,6 +4902,7 @@ final class H extends android.os.Handler { public static final int NOTIFY_KEYGUARD_FLAGS_CHANGED = 56; public static final int NOTIFY_KEYGUARD_TRUSTED_CHANGED = 57; public static final int SET_HAS_OVERLAY_UI = 58; + public static final int RECOMPUTE_FOCUS = 61; /** * Used to denote that an integer field in a message will not be used. @@ -5363,6 +5369,13 @@ public void handleMessage(Message msg) { mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1); } break; + case RECOMPUTE_FOCUS: { + synchronized (mWindowMap) { + updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, + true /* updateInputWindows */); + } + } + break; } if (DEBUG_WINDOW_TRACE) { Slog.v(TAG_WM, "handleMessage: exit"); diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java index 10d241357ff49..a6cada7b42e2c 100644 --- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java @@ -313,6 +313,25 @@ public void testFocusedWindowMultipleDisplays() throws Exception { assertEquals(window1, sWm.mRoot.computeFocusedWindow()); } + @Test + public void testKeyguard_preventsSecondaryDisplayFocus() throws Exception { + final WindowState keyguard = createWindow(null, TYPE_STATUS_BAR, + sWm.getDefaultDisplayContentLocked(), "keyguard"); + assertEquals(keyguard, sWm.mRoot.computeFocusedWindow()); + + // Add a window to a second display, and it should be focused + final DisplayContent dc = createNewDisplay(); + final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win"); + assertEquals(win, sWm.mRoot.computeFocusedWindow()); + + ((TestWindowManagerPolicy)sWm.mPolicy).keyguardShowingAndNotOccluded = true; + try { + assertEquals(keyguard, sWm.mRoot.computeFocusedWindow()); + } finally { + ((TestWindowManagerPolicy)sWm.mPolicy).keyguardShowingAndNotOccluded = false; + } + } + /** * This tests setting the maximum ui width on a display. */ diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java index eca27eefb2cd3..27ea325b8d804 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -63,6 +63,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { private static WindowManagerService sWm = null; int rotationToReport = 0; + boolean keyguardShowingAndNotOccluded = false; private Runnable mRunnableWhenAddingSplashScreen; @@ -420,7 +421,7 @@ public void exitKeyguardSecurely(OnKeyguardExitResult callback) { @Override public boolean isKeyguardLocked() { - return false; + return keyguardShowingAndNotOccluded; } @Override @@ -440,7 +441,7 @@ public boolean isKeyguardTrustedLw() { @Override public boolean isKeyguardShowingAndNotOccluded() { - return false; + return keyguardShowingAndNotOccluded; } @Override From 0e50de324160cb3a9900939b780aab884519b2fb Mon Sep 17 00:00:00 2001 From: Todd Kennedy Date: Thu, 3 May 2018 10:05:04 +0100 Subject: [PATCH 15/73] Make safe label more safe * limit the absolute maximum size of the label to 50000 characters [which is probably far more than necessary, but, can be dialed down] * use a string buffer while processing the string [instead of creating multiple string objects] Bug: 62537081 Test: Manual. Install APK in bug and see that it can be uninstalled Change-Id: Ibf63c2691ad7438a123e92110d95b1f50050f8b1 Merged-In: Ibf63c2691ad7438a123e92110d95b1f50050f8b1 (cherry picked from commit 2263da9539daef134395226a2718ba2d7af7547d) --- .../android/content/pm/PackageItemInfo.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java index 11830c294116d..84b779466dbf9 100644 --- a/core/java/android/content/pm/PackageItemInfo.java +++ b/core/java/android/content/pm/PackageItemInfo.java @@ -42,6 +42,9 @@ */ public class PackageItemInfo { private static final float MAX_LABEL_SIZE_PX = 500f; + /** The maximum length of a safe label, in characters */ + private static final int MAX_SAFE_LABEL_LENGTH = 50000; + /** * Public name of this item. From the "android:name" attribute. */ @@ -169,7 +172,8 @@ public CharSequence loadLabel(PackageManager pm) { // If the label contains new line characters it may push the UI // down to hide a part of it. Labels shouldn't have new line // characters, so just truncate at the first time one is seen. - final int labelLength = labelStr.length(); + final int labelLength = Math.min(labelStr.length(), MAX_SAFE_LABEL_LENGTH); + final StringBuffer sb = new StringBuffer(labelLength); int offset = 0; while (offset < labelLength) { final int codePoint = labelStr.codePointAt(offset); @@ -181,14 +185,19 @@ public CharSequence loadLabel(PackageManager pm) { break; } // replace all non-break space to " " in order to be trimmed + final int charCount = Character.charCount(codePoint); if (type == Character.SPACE_SEPARATOR) { - labelStr = labelStr.substring(0, offset) + " " + labelStr.substring(offset + - Character.charCount(codePoint)); + sb.append(' '); + } else { + sb.append(labelStr.charAt(offset)); + if (charCount == 2) { + sb.append(labelStr.charAt(offset + 1)); + } } - offset += Character.charCount(codePoint); + offset += charCount; } - labelStr = labelStr.trim(); + labelStr = sb.toString().trim(); if (labelStr.isEmpty()) { return packageName; } From 6ab2779351c4d68e79f39d6858bbc07649637897 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Mon, 21 May 2018 13:59:23 -0700 Subject: [PATCH 16/73] ResStringPool: Prevenet boot loop from se fix Changes the logs adding in a previous security fix to warnings so devices with malformed APKs currently on them will not undergo DOS when they are upgraded to P. Bug: 79724567 Test: run cts -m CtsAppSecurityHostTestCases \ -t android.appsecurity.cts.CorruptApkTests Change-Id: Ied54e4bb14abdaf79da562022c7ea6075187c1f8 (cherry picked from commit f05f47b2c1838529e682ad8f931d3da72244b1a1) (cherry picked from commit c31cf80008fdb06ea8e1eab9764096653e7854b1) --- libs/androidfw/ResourceTypes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 401c7b070188c..c78554f340fc5 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -459,7 +459,7 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) // The chunk must be at least the size of the string pool header. if (size < sizeof(ResStringPool_header)) { - LOG_ALWAYS_FATAL("Bad string block: data size %zu is too small to be a string block", size); + ALOGW("Bad string block: data size %zu is too small to be a string block", size); return (mError=BAD_TYPE); } @@ -469,7 +469,7 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) if (validate_chunk(reinterpret_cast(data), sizeof(ResStringPool_header), reinterpret_cast(data) + size, "ResStringPool_header") != NO_ERROR) { - LOG_ALWAYS_FATAL("Bad string block: malformed block dimensions"); + ALOGW("Bad string block: malformed block dimensions"); return (mError=BAD_TYPE); } From e87a7c9dc05c41b4a82929ca43133fbd584628c7 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Wed, 30 May 2018 12:17:01 -0700 Subject: [PATCH 17/73] Fix DynamicRefTable::load security bug DynamicRefTables parsed from apks are missing bounds checks that prevent buffer overflows. This changes verifies the bounds of the header before attempting to preform operations on the chunk. Bug: 79488511 Test: run cts -m CtsAppSecurityHostTestCases \ -t android.appsecurity.cts.CorruptApkTests Change-Id: I02c8ad957da244fce777ac68a482e4e8fa70f846 Merged-In: I02c8ad957da244fce777ac68a482e4e8fa70f846 (cherry picked from commit 18a6ada4aa136da4f50f03fff91d61d448ced195) --- libs/androidfw/ResourceTypes.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index c78554f340fc5..4813b4c83d041 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -6576,8 +6576,16 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, } } else if (ctype == RES_TABLE_LIBRARY_TYPE) { + if (group->dynamicRefTable.entries().size() == 0) { - status_t err = group->dynamicRefTable.load((const ResTable_lib_header*) chunk); + const ResTable_lib_header* lib = (const ResTable_lib_header*) chunk; + status_t err = validate_chunk(&lib->header, sizeof(*lib), + endPos, "ResTable_lib_header"); + if (err != NO_ERROR) { + return (mError=err); + } + + err = group->dynamicRefTable.load(lib); if (err != NO_ERROR) { return (mError=err); } From d14a122e4f02e286e3485e6cfcd018247d879755 Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Tue, 12 Jun 2018 13:01:42 -0700 Subject: [PATCH 18/73] Backport Prevent shortcut info package name spoofing Test: cts-tradefed run cts -m CtsShortcutManagerTestCases -t android.content.pm.cts.shortcutmanager.ShortcutManagerFakingPublisherTest Bug: 109824443 Change-Id: I90443973aaef157d357b98b739572866125b2bbc Merged-In: I78948446a63b428ae750464194558fd44a658493 (cherry picked from commit 9e21579a11219581a0c08ff5dd6ac4dc22e988a4) --- .../android/server/pm/ShortcutService.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 15d20716a3223..83b817559c2a0 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -131,6 +131,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Predicate; @@ -1534,6 +1535,24 @@ private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) { "Ephemeral apps can't use ShortcutManager"); } + private void verifyShortcutInfoPackage(String callerPackage, ShortcutInfo si) { + if (si == null) { + return; + } + if (!Objects.equals(callerPackage, si.getPackage())) { + android.util.EventLog.writeEvent(0x534e4554, "109824443", -1, ""); + throw new SecurityException("Shortcut package name mismatch"); + } + } + + private void verifyShortcutInfoPackages( + String callerPackage, List list) { + final int size = list.size(); + for (int i = 0; i < size; i++) { + verifyShortcutInfoPackage(callerPackage, list.get(i)); + } + } + // Overridden in unit tests to execute r synchronously. void injectPostToHandler(Runnable r) { mHandler.post(r); @@ -1681,6 +1700,7 @@ public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcu verifyCaller(packageName, userId); final List newShortcuts = (List) shortcutInfoList.getList(); + verifyShortcutInfoPackages(packageName, newShortcuts); final int size = newShortcuts.size(); synchronized (mLock) { @@ -1732,6 +1752,7 @@ public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInf verifyCaller(packageName, userId); final List newShortcuts = (List) shortcutInfoList.getList(); + verifyShortcutInfoPackages(packageName, newShortcuts); final int size = newShortcuts.size(); synchronized (mLock) { @@ -1812,6 +1833,7 @@ public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcu verifyCaller(packageName, userId); final List newShortcuts = (List) shortcutInfoList.getList(); + verifyShortcutInfoPackages(packageName, newShortcuts); final int size = newShortcuts.size(); synchronized (mLock) { @@ -1871,6 +1893,7 @@ public Intent createShortcutResultIntent(String packageName, ShortcutInfo shortc Preconditions.checkNotNull(shortcut); Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); verifyCaller(packageName, userId); + verifyShortcutInfoPackage(packageName, shortcut); final Intent ret; synchronized (mLock) { @@ -1892,6 +1915,7 @@ public Intent createShortcutResultIntent(String packageName, ShortcutInfo shortc private boolean requestPinItem(String packageName, int userId, ShortcutInfo shortcut, AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent) { verifyCaller(packageName, userId); + verifyShortcutInfoPackage(packageName, shortcut); final boolean ret; synchronized (mLock) { From 44f897a21c7cc383554f3b7e4463488c8f0c84a1 Mon Sep 17 00:00:00 2001 From: Arthur Ishiguro Date: Mon, 25 Jun 2018 11:31:33 -0700 Subject: [PATCH 19/73] Resolve inconsistent parcel read in NanoAppFilter Bug: 77599679 Test: Compile only Change-Id: Ib417a5cb4d51744442d2fb14437cabbe5fd1c266 (cherry picked from commit abe5a73a4a81e312a1690fbc10a6b99ce98b699a) --- core/java/android/hardware/location/NanoAppFilter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/java/android/hardware/location/NanoAppFilter.java b/core/java/android/hardware/location/NanoAppFilter.java index bf35a3d6fbd69..c44094908f3c2 100644 --- a/core/java/android/hardware/location/NanoAppFilter.java +++ b/core/java/android/hardware/location/NanoAppFilter.java @@ -83,7 +83,7 @@ private NanoAppFilter(Parcel in) { mAppId = in.readLong(); mAppVersion = in.readInt(); mVersionRestrictionMask = in.readInt(); - mAppIdVendorMask = in.readInt(); + mAppIdVendorMask = in.readLong(); } public int describeContents() { @@ -91,7 +91,6 @@ public int describeContents() { } public void writeToParcel(Parcel out, int flags) { - out.writeLong(mAppId); out.writeInt(mAppVersion); out.writeInt(mVersionRestrictionMask); From 43631a43e2a87810aadd6532005c1873d7d3f3ab Mon Sep 17 00:00:00 2001 From: Bernie Innocenti Date: Mon, 28 May 2018 22:04:37 +0900 Subject: [PATCH 20/73] vpn: allow IPSec traffic through Always-on VPN This won't leak any traffic outside the VPN as long as there are no processes owned by uid 0 which generate network traffic (which is currently the case). Bug: 69873852 Test: compared the output of 'adb shell ip rule show' before and after Test: runtest -x frameworks/base/tests/net/java/com/android/server/connectivity/VpnTest.java Test: local CTS tests run: android.net.cts.VpnServiceTest Test: local CTS tests run: com.android.cts.devicepolicy.MixedDeviceOwnerTest Change-Id: I8758e576c9d961d73f62bfcf0559dd7ecee6e8e6 Merged-In: I8758e576c9d961d73f62bfcf0559dd7ecee6e8e6 Merged-In: I1f9b78c8f828ec2df7aba71b39d62be0c4db2550 Merged-In: I8edeb0942e661c8385ff0cd3fdb72e6f62a8f218 (cherry picked from commit 00000fe55a4729f8339afdc7eab5c970b2549813) (cherry picked from commit ef2910dc709d698b6476e8d462c608d04c784a26) --- .../com/android/server/connectivity/Vpn.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 56cff7c715d63..d51e1f7386597 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -94,8 +94,6 @@ import com.android.server.LocalServices; import com.android.server.net.BaseNetworkObserver; -import libcore.io.IoUtils; - import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -114,6 +112,8 @@ import java.util.TreeSet; import java.util.concurrent.atomic.AtomicInteger; +import libcore.io.IoUtils; + /** * @hide */ @@ -1184,6 +1184,18 @@ private void setVpnForcedWithExemptionsLocked(boolean enforce, /* allowedApplications */ null, /* disallowedApplications */ exemptedPackages); + // The UID range of the first user (0-99999) would block the IPSec traffic, which comes + // directly from the kernel and is marked as uid=0. So we adjust the range to allow + // it through (b/69873852). + for (UidRange range : addedRanges) { + if (range.start == 0) { + addedRanges.remove(range); + if (range.stop != 0) { + addedRanges.add(new UidRange(1, range.stop)); + } + } + } + removedRanges.removeAll(addedRanges); addedRanges.removeAll(mBlockedUsers); } From 70346bab0c0db43f488a5a8534012fdb7085fa60 Mon Sep 17 00:00:00 2001 From: Robert Shih Date: Mon, 9 Jul 2018 13:38:31 -0700 Subject: [PATCH 21/73] Fix TrackInfo parcel write Bug: 77600398 Change-Id: Ia316f1c5dc4879f6851fdb78fe8b9039579be7bc (cherry picked from commit 0d2dc943dcaa3d7c8479e22ae62be9753ea2643c) --- media/java/android/media/MediaPlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 0d99473c69b93..f384ea56ca216 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -2363,10 +2363,10 @@ public int describeContents() { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mTrackType); + dest.writeString(mFormat.getString(MediaFormat.KEY_MIME)); dest.writeString(getLanguage()); if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { - dest.writeString(mFormat.getString(MediaFormat.KEY_MIME)); dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT)); dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_DEFAULT)); dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE)); From 59bc89a7099c89f4a0504a7c3b70f2ab4625b9e0 Mon Sep 17 00:00:00 2001 From: Joe Maples Date: Wed, 3 Jan 2018 21:53:01 -0500 Subject: [PATCH 22/73] SystemUI: Smart Pixels [1/2] Disables a percentage of pixels on screen to reduce power consumption. If enabled with battery saver, don't scale brightness at 0.5f for UX. Includes: - Option to enable on battery saver - User chosen grid - Burn-in protection Configurable via overlay and disabled by defualt: "config_enableSmartPixels" Change-Id: Id3c78548cb090ab2da11f543da31c5a408fb9fe9 --- core/java/android/provider/Settings.java | 32 +++ core/res/res/values/aos_config.xml | 3 + core/res/res/values/aos_symbols.xml | 3 + packages/SystemUI/AndroidManifest.xml | 9 + .../android/systemui/smartpixels/Grids.java | 148 +++++++++++ .../smartpixels/SmartPixelsReceiver.java | 128 +++++++++ .../smartpixels/SmartPixelsService.java | 249 ++++++++++++++++++ .../display/DisplayPowerController.java | 20 +- 8 files changed, 588 insertions(+), 4 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/smartpixels/Grids.java create mode 100644 packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsReceiver.java create mode 100644 packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsService.java diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c0fe253e85d8c..c1a67377efa82 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3719,6 +3719,38 @@ public boolean validate(String value) { /** @hide */ public static final Validator SHOW_WEB_SUGGESTIONS_VALIDATOR = sBooleanValidator; + /** + * Whether to allow notifications with the screen on or DayDreams. + * The value is boolean (1 or 0). Default will always be false. + * @hide + */ + public static final String NOTIFICATION_LIGHT_SCREEN_ON = + "notification_light_screen_on_enable"; + + /** + * Whether to enable Smart Pixels + * @hide + */ + public static final String SMART_PIXELS_ENABLE = "smart_pixels_enable"; + + /** + * Smart Pixels pattern + * @hide + */ + public static final String SMART_PIXELS_PATTERN = "smart_pixels_pattern"; + + /** + * Smart Pixels Shift Timeout + * @hide + */ + public static final String SMART_PIXELS_SHIFT_TIMEOUT = "smart_pixels_shift_timeout"; + + /** + * Whether Smart Pixels should enable on power saver mode + * @hide + */ + public static final String SMART_PIXELS_ON_POWER_SAVE = "smart_pixels_on_power_save"; + /** * Whether the notification LED should repeatedly flash when a notification is * pending. The value is boolean (1 or 0). diff --git a/core/res/res/values/aos_config.xml b/core/res/res/values/aos_config.xml index 2b24ffae9c62d..c370c3f4bdff9 100644 --- a/core/res/res/values/aos_config.xml +++ b/core/res/res/values/aos_config.xml @@ -135,4 +135,7 @@ false false false + + + false diff --git a/core/res/res/values/aos_symbols.xml b/core/res/res/values/aos_symbols.xml index 4c8b99bde37ec..458dddfd1c0e7 100644 --- a/core/res/res/values/aos_symbols.xml +++ b/core/res/res/values/aos_symbols.xml @@ -175,4 +175,7 @@ + + + diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index e790c0f26bbfa..83dbf3cc5b514 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -294,6 +294,15 @@ android:process=":screenrecord" android:exported="false" /> + + + + + + + + diff --git a/packages/SystemUI/src/com/android/systemui/smartpixels/Grids.java b/packages/SystemUI/src/com/android/systemui/smartpixels/Grids.java new file mode 100644 index 0000000000000..e29381c384859 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/smartpixels/Grids.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2015, Sergii Pylypenko + * (c) 2018, Joe Maples + * (c) 2018, CarbonROM + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of screen-dimmer-pixel-filter nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package com.android.systemui.smartpixels; + +public class Grids { + + public static final int GridSize = 64; + public static final int GridSideSize = 8; + + public static String[] PatternNames = new String[] { + "12%", + "25%", + "38%", + "50%", + "62%", + "75%", + "88%", + }; + + public static byte[][] Patterns = new byte[][] { + { + 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, + 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, 0, 0, 0, + }, + { + 1, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 1, 0, + 0, 1, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 1, 0, + 0, 1, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 1, + }, + { + 1, 0, 0, 0, 1, 0, 0, 0, + 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 1, 0, 0, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 0, 0, 1, 0, 0, 0, + 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 1, 0, 0, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, + }, + { + 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, + }, + { + 0, 1, 1, 1, 0, 1, 1, 1, + 1, 0, 1, 0, 1, 0, 1, 0, + 1, 1, 0, 1, 1, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 1, 1, 0, 1, 1, 1, + 1, 0, 1, 0, 1, 0, 1, 0, + 1, 1, 0, 1, 1, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, + }, + { + 0, 1, 1, 1, 0, 1, 1, 1, + 1, 1, 0, 1, 1, 1, 0, 1, + 1, 0, 1, 1, 1, 0, 1, 1, + 1, 1, 1, 0, 1, 1, 1, 0, + 0, 1, 1, 1, 0, 1, 1, 1, + 1, 1, 0, 1, 1, 1, 0, 1, + 1, 0, 1, 1, 1, 0, 1, 1, + 1, 1, 1, 0, 1, 1, 1, 0, + }, + { + 0, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0, 1, + 1, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, 1, 1, + 1, 1, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 0, 1, 1, 1, + }, + }; + + // Indexes to shift screen pattern in both vertical and horizontal directions + public static byte[] GridShift = new byte[] { + 0, 1, 8, 9, 2, 3, 10, 11, + 4, 5, 12, 13, 6, 7, 14, 15, + 16, 17, 24, 25, 18, 19, 26, 27, + 20, 21, 28, 29, 22, 23, 30, 31, + 32, 33, 40, 41, 34, 35, 42, 43, + 36, 37, 44, 45, 38, 39, 46, 47, + 48, 49, 56, 57, 50, 51, 58, 59, + 52, 53, 60, 61, 54, 55, 62, 63, + }; + + public static int[] ShiftTimeouts = new int[] { // In milliseconds + 15 * 1000, + 30 * 1000, + 60 * 1000, + 2 * 60 * 1000, + 5 * 60 * 1000, + 10 * 60 * 1000, + 20 * 60 * 1000, + 30 * 60 * 1000, + 60 * 60 * 1000, + }; + +} diff --git a/packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsReceiver.java b/packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsReceiver.java new file mode 100644 index 0000000000000..798323af73070 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsReceiver.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2018 CarbonROM + * + * 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.smartpixels; + +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.PowerManager; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; + +// Handles Boot, PowerSave, and Settings changes +public class SmartPixelsReceiver extends BroadcastReceiver { + private static final String TAG = "SmartPixelsReceiver"; + private Handler mHandler = new Handler(); + private SettingsObserver mSettingsObserver; + private Context mContext; + private int mSmartPixelsEnable; + private int mSmartPixelsOnPowerSave; + private int mLowPowerMode; + private boolean mSmartPixelsRunning = false; + + private class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + void observe() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SMART_PIXELS_ENABLE), + false, this, UserHandle.USER_CURRENT); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SMART_PIXELS_ON_POWER_SAVE), + false, this, UserHandle.USER_CURRENT); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SMART_PIXELS_PATTERN), + false, this, UserHandle.USER_CURRENT); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SMART_PIXELS_SHIFT_TIMEOUT), + false, this, UserHandle.USER_CURRENT); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.LOW_POWER_MODE), + false, this, UserHandle.USER_ALL); + update(); + } + + @Override + public void onChange(boolean selfChange) { + update(); + } + + public void update() { + final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + Intent smartPixels = new Intent(mContext, com.android.systemui.smartpixels.SmartPixelsService.class); + ContentResolver resolver = mContext.getContentResolver(); + + mSmartPixelsEnable = Settings.System.getIntForUser( + resolver, Settings.System.SMART_PIXELS_ENABLE, + 0, UserHandle.USER_CURRENT); + mSmartPixelsOnPowerSave = Settings.System.getIntForUser( + resolver, Settings.System.SMART_PIXELS_ON_POWER_SAVE, + 0, UserHandle.USER_CURRENT); + mLowPowerMode = Settings.Global.getInt( + resolver, Settings.Global.LOW_POWER_MODE, 0); + + if ((mSmartPixelsEnable == 0) && (mSmartPixelsOnPowerSave != 0)) { + if ((mLowPowerMode != 0) && !mSmartPixelsRunning) { + mContext.startService(smartPixels); + mSmartPixelsRunning = true; + Log.d(TAG, "Started Smart Pixels service by LowPowerMode enable"); + } else if ((mLowPowerMode == 0) && mSmartPixelsRunning) { + mContext.stopService(smartPixels); + mSmartPixelsRunning = false; + Log.d(TAG, "Stopped Smart Pixels service by LowPowerMode disable"); + } else if ((mLowPowerMode != 0) && mSmartPixelsRunning) { + mContext.stopService(smartPixels); + mContext.startService(smartPixels); + Log.d(TAG, "Restarted Smart Pixels service by LowPowerMode enable"); + } + } else if ((mSmartPixelsEnable != 0) && !mSmartPixelsRunning) { + mContext.startService(smartPixels); + mSmartPixelsRunning = true; + Log.d(TAG, "Started Smart Pixels service by enable"); + } else if ((mSmartPixelsEnable == 0) && mSmartPixelsRunning) { + mContext.stopService(smartPixels); + mSmartPixelsRunning = false; + Log.d(TAG, "Stopped Smart Pixels service by disable"); + } else if ((mSmartPixelsEnable != 0) && mSmartPixelsRunning) { + mContext.stopService(smartPixels); + mContext.startService(smartPixels); + Log.d(TAG, "Restarted Smart Pixels service"); + } + } + } + + @Override + public void onReceive(final Context context, Intent intent) { + mContext = context; + try { + if (mSettingsObserver == null) { + mSettingsObserver = new SettingsObserver(mHandler); + mSettingsObserver.observe(); + } + mSettingsObserver.update(); + } catch (Exception e) { + Log.e(TAG, "Can't start load average service", e); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsService.java b/packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsService.java new file mode 100644 index 0000000000000..87ac2f61c1089 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsService.java @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2015, Sergii Pylypenko + * (c) 2018, Joe Maples + * (c) 2018, CarbonROM + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of screen-dimmer-pixel-filter nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package com.android.systemui.smartpixels; + +import android.Manifest; +import android.app.Service; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.Shader; +import android.graphics.drawable.BitmapDrawable; +import android.os.Handler; +import android.os.IBinder; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; + +import com.android.systemui.R; + +public class SmartPixelsService extends Service { + public static final String LOG = "SmartPixelsService"; + + private WindowManager windowManager; + private ImageView view = null; + private Bitmap bmp; + + private boolean destroyed = false; + private boolean intentProcessed = false; + public static boolean running = false; + + private int startCounter = 0; + private Context mContext; + + // Pixel Filter Settings + private int mPattern = 3; + private int mShiftTimeout = 4; + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + running = true; + mContext = this; + updateSettings(); + Log.d(LOG, "Service started"); + startFilter(); + } + + public void startFilter() { + if (view != null) { + return; + } + + windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); + + view = new ImageView(this); + DisplayMetrics metrics = new DisplayMetrics(); + windowManager.getDefaultDisplay().getRealMetrics(metrics); + bmp = Bitmap.createBitmap(Grids.GridSideSize, Grids.GridSideSize, Bitmap.Config.ARGB_4444); + + updatePattern(); + BitmapDrawable draw = new BitmapDrawable(bmp); + draw.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); + draw.setFilterBitmap(false); + draw.setAntiAlias(false); + draw.setTargetDensity(metrics.densityDpi); + + view.setBackground(draw); + + WindowManager.LayoutParams params = getLayoutParams(); + try { + windowManager.addView(view, params); + } catch (Exception e) { + running = false; + view = null; + return; + } + + + startCounter++; + final int handlerStartCounter = startCounter; + final Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + @Override + public void run() { + if (view == null || destroyed || handlerStartCounter != startCounter) { + return; + } + updatePattern(); + view.invalidate(); + if (!destroyed) { + handler.postDelayed(this, Grids.ShiftTimeouts[mShiftTimeout]); + } + } + }, Grids.ShiftTimeouts[mShiftTimeout]); + } + + public void stopFilter() { + if (view == null) { + return; + } + + startCounter++; + + windowManager.removeView(view); + view = null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (Intent.ACTION_DELETE.equals(intent.getAction()) || + (intentProcessed && Intent.ACTION_INSERT.equals(intent.getAction()))) { + Log.d(LOG, "Service got shutdown intent"); + stopSelf(); + intentProcessed = true; + return START_NOT_STICKY; + } + + intentProcessed = true; + Log.d(LOG, "Service got intent " + intent.getAction()); + return START_STICKY; + } + + @Override + public void onDestroy() { + super.onDestroy(); + destroyed = true; + stopFilter(); + Log.d(LOG, "Service stopped"); + running = false; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + Log.d(LOG, "Screen orientation changed, updating window layout"); + WindowManager.LayoutParams params = getLayoutParams(); + windowManager.updateViewLayout(view, params); + } + + private WindowManager.LayoutParams getLayoutParams() + { + Point displaySize = new Point(); + windowManager.getDefaultDisplay().getRealSize(displaySize); + Point windowSize = new Point(); + windowManager.getDefaultDisplay().getRealSize(windowSize); + Resources res = getResources(); + int mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height); + displaySize.x += displaySize.x - windowSize.x + (mStatusBarHeight * 2); + displaySize.y += displaySize.y - windowSize.y + (mStatusBarHeight * 2); + + WindowManager.LayoutParams params = new WindowManager.LayoutParams( + displaySize.x, + displaySize.y, + 0, + 0, + WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | + WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN | + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, + PixelFormat.TRANSPARENT + ); + + // Use the rounded corners overlay to hide it from screenshots. See 132c9f514. + params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; + + params.dimAmount = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE; + params.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE; + return params; + } + + private int getShift() { + long shift = (System.currentTimeMillis() / Grids.ShiftTimeouts[mShiftTimeout]) % Grids.GridSize; + return Grids.GridShift[(int)shift]; + } + + private void updatePattern() { + int shift = getShift(); + int shiftX = shift % Grids.GridSideSize; + int shiftY = shift / Grids.GridSideSize; + for (int i = 0; i < Grids.GridSize; i++) { + int x = (i + shiftX) % Grids.GridSideSize; + int y = ((i / Grids.GridSideSize) + shiftY) % Grids.GridSideSize; + int color = (Grids.Patterns[mPattern][i] == 0) ? Color.TRANSPARENT : Color.BLACK; + bmp.setPixel(x, y, color); + } + } + + private void updateSettings() { + mPattern = Settings.System.getIntForUser( + mContext.getContentResolver(), Settings.System.SMART_PIXELS_PATTERN, + 3, UserHandle.USER_CURRENT); + mShiftTimeout = Settings.System.getIntForUser( + mContext.getContentResolver(), Settings.System.SMART_PIXELS_SHIFT_TIMEOUT, + 4, UserHandle.USER_CURRENT); + } +} diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 95fb814cc0572..0be446d0c994d 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -301,6 +301,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call static final int SCREEN_OFF_CRT = 1; static final int SCREEN_OFF_SCALE = 2; + final ContentResolver cr; + /** * Creates the display power controller. */ @@ -316,6 +318,8 @@ public DisplayPowerController(Context context, mBlanker = blanker; mContext = context; + cr = mContext.getContentResolver(); + final Resources resources = context.getResources(); final int screenBrightnessSettingMinimum = clampAbsoluteBrightness(resources.getInteger( com.android.internal.R.integer.config_screenBrightnessSettingMinimum)); @@ -553,7 +557,6 @@ private int getScreenAnimationModeForDisplayState(int displayState) { private void initialize() { // Initialize the power state object for the default display. // In the future, we might manage multiple displays independently. - final ContentResolver cr = mContext.getContentResolver(); final ContentObserver observer = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange, Uri uri) { @@ -806,10 +809,19 @@ private void updatePowerState() { mAppliedDimming = false; } - // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor - // as long as it is above the minimum threshold. + // If low power mode is enabled and Smart Pixels Service is stopped, + // scale brightness by screenLowPowerBrightnessFactor + // as long as it is above the minimum threshold + int mSmartPixelsEnable = Settings.System.getIntForUser( + cr, Settings.System.SMART_PIXELS_ENABLE, + 0, UserHandle.USER_CURRENT); + int mSmartPixelsOnPowerSave = Settings.System.getIntForUser( + cr, Settings.System.SMART_PIXELS_ON_POWER_SAVE, + 0, UserHandle.USER_CURRENT); + if (mPowerRequest.lowPowerMode) { - if (brightness > mScreenBrightnessRangeMinimum) { + if ((brightness > mScreenBrightnessRangeMinimum) && + ((mSmartPixelsEnable == 0) || (mSmartPixelsOnPowerSave == 0))) { final float brightnessFactor = Math.min(mPowerRequest.screenLowPowerBrightnessFactor, 1); final int lowPowerBrightness = (int) (brightness * brightnessFactor); From 8a070a9fe27d2ec082a0e65ee3b255b8c3b81dae Mon Sep 17 00:00:00 2001 From: Adin Kwok Date: Sat, 7 Apr 2018 20:08:45 -0700 Subject: [PATCH 23/73] SystemUI: Add Smart Pixels tile Single tap enables/disables. Long press opens Smart Pixels settings. User is not allowed to enable or disable when Smart Pixels has been auto-enabled on battery saver. Change-Id: I535c84a0ca6360e1db351391e9d0fb9095896ee3 --- .../res/drawable/ic_qs_smart_pixels.xml | 8 + packages/SystemUI/res/values/aos_strings.xml | 4 + .../systemui/qs/tileimpl/QSFactoryImpl.java | 2 + .../systemui/qs/tiles/SmartPixelsTile.java | 148 ++++++++++++++++++ 4 files changed, 162 insertions(+) create mode 100644 packages/SystemUI/res/drawable/ic_qs_smart_pixels.xml create mode 100644 packages/SystemUI/src/com/android/systemui/qs/tiles/SmartPixelsTile.java diff --git a/packages/SystemUI/res/drawable/ic_qs_smart_pixels.xml b/packages/SystemUI/res/drawable/ic_qs_smart_pixels.xml new file mode 100644 index 0000000000000..4aa750d3be429 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_smart_pixels.xml @@ -0,0 +1,8 @@ + + + + diff --git a/packages/SystemUI/res/values/aos_strings.xml b/packages/SystemUI/res/values/aos_strings.xml index deb85c8b9c3cd..ecbe1dba5e884 100644 --- a/packages/SystemUI/res/values/aos_strings.xml +++ b/packages/SystemUI/res/values/aos_strings.xml @@ -201,4 +201,8 @@ Location reporting: sensors only mode. Location reporting: high accuracy mode. + + Smart pixels + Auto-enabled smart pixels + diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index cbe92be10fd6c..b6b6d4a4a5a97 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -48,6 +48,7 @@ import com.android.systemui.qs.tiles.VolumeTile; import com.android.systemui.qs.tiles.HWKeysTile; import com.android.systemui.qs.tiles.ThemeTile; +import com.android.systemui.qs.tiles.SmartPixelsTile; import com.android.systemui.qs.tiles.UserTile; import com.android.systemui.qs.tiles.WeatherTile; import com.android.systemui.qs.tiles.WifiTile; @@ -93,6 +94,7 @@ public QSTile createTile(String tileSpec) { else if (tileSpec.equals("navigation")) return new NavigationBarTile(mHost); else if (tileSpec.equals("weather")) return new WeatherTile(mHost); else if (tileSpec.equals("theme")) return new ThemeTile(mHost); + else if (tileSpec.equals("smartpixels")) return new SmartPixelsTile(mHost); // Intent tiles. else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(mHost, tileSpec); else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(mHost, tileSpec); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SmartPixelsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SmartPixelsTile.java new file mode 100644 index 0000000000000..758eac70b5368 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SmartPixelsTile.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2018 CarbonROM + * Copyright (C) 2018 Adin Kwok (adinkwok) + * + * 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.qs.tiles; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.PowerManager; +import android.os.UserHandle; +import android.provider.Settings; +import android.service.quicksettings.Tile; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.plugins.qs.QSTile.BooleanState; +import com.android.systemui.qs.GlobalSetting; +import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.R; + +public class SmartPixelsTile extends QSTileImpl { + private static final ComponentName SMART_PIXELS_SETTING_COMPONENT = new ComponentName( + "com.android.settings", "com.android.settings.Settings$SmartPixelsActivity"); + + private static final Intent SMART_PIXELS_SETTINGS = + new Intent().setComponent(SMART_PIXELS_SETTING_COMPONENT); + + private boolean mSmartPixelsEnable; + private boolean mSmartPixelsOnPowerSave; + private boolean mLowPowerMode; + private boolean mListening; + + public SmartPixelsTile(QSHost host) { + super(host); + } + + @Override + public BooleanState newTileState() { + return new BooleanState(); + } + + @Override + public void handleSetListening(boolean listening) { + mListening = listening; + if (mListening) { + mContext.registerReceiver(mSmartPixelsReceiver, + new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); + } else { + mContext.unregisterReceiver(mSmartPixelsReceiver); + } + } + + @Override + public boolean isAvailable() { + return mContext.getResources(). + getBoolean(com.android.internal.R.bool.config_enableSmartPixels); + } + + @Override + public void handleClick() { + mSmartPixelsEnable = (Settings.System.getIntForUser( + mContext.getContentResolver(), Settings.System.SMART_PIXELS_ENABLE, + 0, UserHandle.USER_CURRENT) == 1); + mSmartPixelsOnPowerSave = (Settings.System.getIntForUser( + mContext.getContentResolver(), Settings.System.SMART_PIXELS_ON_POWER_SAVE, + 0, UserHandle.USER_CURRENT) == 1); + mLowPowerMode = (Settings.Global.getInt( + mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0) == 1); + if (!mLowPowerMode || !mSmartPixelsOnPowerSave) { + if (!mSmartPixelsEnable) { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.SMART_PIXELS_ENABLE, + 1, UserHandle.USER_CURRENT); + } else { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.SMART_PIXELS_ENABLE, + 0, UserHandle.USER_CURRENT); + } + } + refreshState(); + } + + @Override + public Intent getLongClickIntent() { + return SMART_PIXELS_SETTINGS; + } + + @Override + protected void handleUpdateState(BooleanState state, Object arg) { + mSmartPixelsEnable = (Settings.System.getIntForUser( + mContext.getContentResolver(), Settings.System.SMART_PIXELS_ENABLE, + 0, UserHandle.USER_CURRENT) == 1); + mSmartPixelsOnPowerSave = (Settings.System.getIntForUser( + mContext.getContentResolver(), Settings.System.SMART_PIXELS_ON_POWER_SAVE, + 0, UserHandle.USER_CURRENT) == 1); + mLowPowerMode = (Settings.Global.getInt( + mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0) == 1); + state.icon = ResourceIcon.get(R.drawable.ic_qs_smart_pixels); + if (state.slash == null) { + state.slash = new SlashState(); + } + if (mLowPowerMode && mSmartPixelsOnPowerSave) { + state.label = mContext.getString(R.string.quick_settings_smart_pixels_on_power_save); + state.value = true; + } else if (mSmartPixelsEnable) { + state.label = mContext.getString(R.string.quick_settings_smart_pixels); + state.value = true; + } else { + state.label = mContext.getString(R.string.quick_settings_smart_pixels); + state.value = false; + } + state.slash.isSlashed = !state.value; + state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; + } + + @Override + public CharSequence getTileLabel() { + return mContext.getString(R.string.quick_settings_smart_pixels); + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.LABORATORY; + } + + private BroadcastReceiver mSmartPixelsReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + refreshState(); + } + }; +} From d02415d61f1bb025b0f4c973cadb620d37b12bb9 Mon Sep 17 00:00:00 2001 From: Adin Kwok Date: Thu, 12 Apr 2018 17:02:43 -0700 Subject: [PATCH 24/73] SystemUI: Refactor Smart Pixels tile Allow user to disable Smart Pixels when auto-enabled. Change-Id: Ie8bbd483d52b579aa78c57625d11fb69a60e2fcd --- .../systemui/qs/tiles/SmartPixelsTile.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SmartPixelsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SmartPixelsTile.java index 758eac70b5368..f9cb2efafd38e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SmartPixelsTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SmartPixelsTile.java @@ -82,16 +82,21 @@ public void handleClick() { 0, UserHandle.USER_CURRENT) == 1); mLowPowerMode = (Settings.Global.getInt( mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0) == 1); - if (!mLowPowerMode || !mSmartPixelsOnPowerSave) { - if (!mSmartPixelsEnable) { - Settings.System.putIntForUser(mContext.getContentResolver(), - Settings.System.SMART_PIXELS_ENABLE, - 1, UserHandle.USER_CURRENT); - } else { - Settings.System.putIntForUser(mContext.getContentResolver(), - Settings.System.SMART_PIXELS_ENABLE, - 0, UserHandle.USER_CURRENT); - } + if (mLowPowerMode && mSmartPixelsOnPowerSave) { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.SMART_PIXELS_ON_POWER_SAVE, + 0, UserHandle.USER_CURRENT); + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.SMART_PIXELS_ENABLE, + 0, UserHandle.USER_CURRENT); + } else if (!mSmartPixelsEnable) { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.SMART_PIXELS_ENABLE, + 1, UserHandle.USER_CURRENT); + } else { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.SMART_PIXELS_ENABLE, + 0, UserHandle.USER_CURRENT); } refreshState(); } From d0b34ac1797fef96a92ee6f43f9e685f824c548f Mon Sep 17 00:00:00 2001 From: nickdoh81 Date: Sat, 14 Apr 2018 10:46:32 -0400 Subject: [PATCH 25/73] Fix NullPointerException on systemui restart with smart pixel enabled. - Fix the logic on the QS tile. It was not showing up for devices that actually do support it. Change-Id: Id7e2f38f5264d99a53a3fddf72eb5d82f0105e02 --- .../smartpixels/SmartPixelsService.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsService.java b/packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsService.java index 87ac2f61c1089..c76f7a300ec52 100644 --- a/packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsService.java +++ b/packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsService.java @@ -156,17 +156,22 @@ public void stopFilter() { @Override public int onStartCommand(Intent intent, int flags, int startId) { - if (Intent.ACTION_DELETE.equals(intent.getAction()) || - (intentProcessed && Intent.ACTION_INSERT.equals(intent.getAction()))) { - Log.d(LOG, "Service got shutdown intent"); - stopSelf(); - intentProcessed = true; - return START_NOT_STICKY; + if(intent != null && intent.getAction() != null) { + return START_STICKY; } + else { + if (Intent.ACTION_DELETE.equals(intent.getAction()) || + (intentProcessed && Intent.ACTION_INSERT.equals(intent.getAction()))) { + Log.d(LOG, "Service got shutdown intent"); + stopSelf(); + intentProcessed = true; + return START_NOT_STICKY; + } intentProcessed = true; Log.d(LOG, "Service got intent " + intent.getAction()); - return START_STICKY; + return START_REDELIVER_INTENT; + } } @Override From ff6aa0eb8ba8d0c6cea6db01e4543c6e5cba38e0 Mon Sep 17 00:00:00 2001 From: Alex Cruz Date: Wed, 18 Apr 2018 02:20:28 -0500 Subject: [PATCH 26/73] Move Smart Pixels out of SystemUI Having the smart pixels service in systemui caused a lot of issues. The big one being that if systemui was restarted via the AM hack made famous by Roman, the service can not be restarted by the user unless the devuce is rebooted. This made for a bad user experience. Test: - Enable smart pixels - Restart systemui (adb shell pkill -TERM -f com.android.systemui) Change-Id: If374a03876923e112bc3f4f3032e1682f653c1fc --- core/res/AndroidManifest.xml | 9 +++++++++ packages/SystemUI/AndroidManifest.xml | 9 --------- .../core/java/com/android/server}/smartpixels/Grids.java | 2 +- .../android/server}/smartpixels/SmartPixelsReceiver.java | 4 ++-- .../android/server}/smartpixels/SmartPixelsService.java | 6 ++---- 5 files changed, 14 insertions(+), 16 deletions(-) rename {packages/SystemUI/src/com/android/systemui => services/core/java/com/android/server}/smartpixels/Grids.java (99%) rename {packages/SystemUI/src/com/android/systemui => services/core/java/com/android/server}/smartpixels/SmartPixelsReceiver.java (98%) rename {packages/SystemUI/src/com/android/systemui => services/core/java/com/android/server}/smartpixels/SmartPixelsService.java (98%) diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index b589cb2ff0877..053e927ee8257 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3884,6 +3884,12 @@ + + + + + + @@ -3938,6 +3944,9 @@ + + diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 83dbf3cc5b514..e790c0f26bbfa 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -294,15 +294,6 @@ android:process=":screenrecord" android:exported="false" /> - - - - - - - - diff --git a/packages/SystemUI/src/com/android/systemui/smartpixels/Grids.java b/services/core/java/com/android/server/smartpixels/Grids.java similarity index 99% rename from packages/SystemUI/src/com/android/systemui/smartpixels/Grids.java rename to services/core/java/com/android/server/smartpixels/Grids.java index e29381c384859..b08e61bff7400 100644 --- a/packages/SystemUI/src/com/android/systemui/smartpixels/Grids.java +++ b/services/core/java/com/android/server/smartpixels/Grids.java @@ -31,7 +31,7 @@ * */ -package com.android.systemui.smartpixels; +package com.android.server.smartpixels; public class Grids { diff --git a/packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsReceiver.java b/services/core/java/com/android/server/smartpixels/SmartPixelsReceiver.java similarity index 98% rename from packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsReceiver.java rename to services/core/java/com/android/server/smartpixels/SmartPixelsReceiver.java index 798323af73070..7ca61bad48235 100644 --- a/packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsReceiver.java +++ b/services/core/java/com/android/server/smartpixels/SmartPixelsReceiver.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.smartpixels; +package com.android.server.smartpixels; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -70,7 +70,7 @@ public void onChange(boolean selfChange) { public void update() { final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - Intent smartPixels = new Intent(mContext, com.android.systemui.smartpixels.SmartPixelsService.class); + Intent smartPixels = new Intent(mContext, com.android.server.smartpixels.SmartPixelsService.class); ContentResolver resolver = mContext.getContentResolver(); mSmartPixelsEnable = Settings.System.getIntForUser( diff --git a/packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsService.java b/services/core/java/com/android/server/smartpixels/SmartPixelsService.java similarity index 98% rename from packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsService.java rename to services/core/java/com/android/server/smartpixels/SmartPixelsService.java index c76f7a300ec52..f25522ee7f00b 100644 --- a/packages/SystemUI/src/com/android/systemui/smartpixels/SmartPixelsService.java +++ b/services/core/java/com/android/server/smartpixels/SmartPixelsService.java @@ -31,7 +31,7 @@ * */ -package com.android.systemui.smartpixels; +package com.android.server.smartpixels; import android.Manifest; import android.app.Service; @@ -59,8 +59,6 @@ import android.view.WindowManager; import android.widget.ImageView; -import com.android.systemui.R; - public class SmartPixelsService extends Service { public static final String LOG = "SmartPixelsService"; @@ -198,7 +196,7 @@ private WindowManager.LayoutParams getLayoutParams() Point windowSize = new Point(); windowManager.getDefaultDisplay().getRealSize(windowSize); Resources res = getResources(); - int mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height); + int mStatusBarHeight = res.getDimensionPixelOffset(com.android.internal.R.dimen.status_bar_height); displaySize.x += displaySize.x - windowSize.x + (mStatusBarHeight * 2); displaySize.y += displaySize.y - windowSize.y + (mStatusBarHeight * 2); From 04c0ce13a25293aca058db20b3fb35a34d8573eb Mon Sep 17 00:00:00 2001 From: Adin Kwok Date: Wed, 18 Apr 2018 01:05:27 -0700 Subject: [PATCH 27/73] Smart Pixels: Switch to registered receiver Switching to a registered receiver allows to properly handle updates on enabling of battery saver mode and switching of users. Also only update screen filter with burn-in protection when the device is in an interactive state. Test: Service starts after rebooting with it enabled Test: Service starts on battery saver mode (user toggle) Test: Service starts on battery saver mode (auto-enabled) Test: Service re-adjusts on user switch to current user settings Test: Filter updates after selected timeout Change-Id: Iced17fd5cc49e0163754bf75782f8465b54e859b --- core/res/AndroidManifest.xml | 6 - .../smartpixels/SmartPixelsReceiver.java | 193 ++++++++++-------- .../smartpixels/SmartPixelsService.java | 27 +-- .../java/com/android/server/SystemServer.java | 3 + 4 files changed, 113 insertions(+), 116 deletions(-) diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 053e927ee8257..2692c5cd5d457 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3884,12 +3884,6 @@ - - - - - - diff --git a/services/core/java/com/android/server/smartpixels/SmartPixelsReceiver.java b/services/core/java/com/android/server/smartpixels/SmartPixelsReceiver.java index 7ca61bad48235..27172a5d12ba2 100644 --- a/services/core/java/com/android/server/smartpixels/SmartPixelsReceiver.java +++ b/services/core/java/com/android/server/smartpixels/SmartPixelsReceiver.java @@ -20,6 +20,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.database.ContentObserver; import android.os.Handler; import android.os.PowerManager; @@ -27,102 +28,114 @@ import android.provider.Settings; import android.util.Log; -// Handles Boot, PowerSave, and Settings changes +// Handles Runtime start, PowerSave, and Settings changes public class SmartPixelsReceiver extends BroadcastReceiver { - private static final String TAG = "SmartPixelsReceiver"; - private Handler mHandler = new Handler(); - private SettingsObserver mSettingsObserver; - private Context mContext; - private int mSmartPixelsEnable; - private int mSmartPixelsOnPowerSave; - private int mLowPowerMode; - private boolean mSmartPixelsRunning = false; + private static final String TAG = "SmartPixelsReceiver"; - private class SettingsObserver extends ContentObserver { - SettingsObserver(Handler handler) { - super(handler); - } + private Context mContext; + private Handler mHandler; + private ContentResolver mResolver; + private final PowerManager mPowerManager; + private SettingsObserver mSettingsObserver; + private Intent mSmartPixelsService; - void observe() { - ContentResolver resolver = mContext.getContentResolver(); - resolver.registerContentObserver(Settings.System.getUriFor( - Settings.System.SMART_PIXELS_ENABLE), - false, this, UserHandle.USER_CURRENT); - resolver.registerContentObserver(Settings.System.getUriFor( - Settings.System.SMART_PIXELS_ON_POWER_SAVE), - false, this, UserHandle.USER_CURRENT); - resolver.registerContentObserver(Settings.System.getUriFor( - Settings.System.SMART_PIXELS_PATTERN), - false, this, UserHandle.USER_CURRENT); - resolver.registerContentObserver(Settings.System.getUriFor( - Settings.System.SMART_PIXELS_SHIFT_TIMEOUT), - false, this, UserHandle.USER_CURRENT); - resolver.registerContentObserver(Settings.Global.getUriFor( - Settings.Global.LOW_POWER_MODE), - false, this, UserHandle.USER_ALL); - update(); - } + private boolean mSmartPixelsEnable; + private boolean mSmartPixelsOnPowerSave; + private boolean mPowerSaveEnable; + private boolean mSmartPixelsRunning = false; - @Override - public void onChange(boolean selfChange) { - update(); - } + public SmartPixelsReceiver(Context context) { + mContext = context; + mHandler = new Handler(); + mResolver = mContext.getContentResolver(); + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mSmartPixelsService = new Intent(mContext, + com.android.server.smartpixels.SmartPixelsService.class); - public void update() { - final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - Intent smartPixels = new Intent(mContext, com.android.server.smartpixels.SmartPixelsService.class); - ContentResolver resolver = mContext.getContentResolver(); + registerReceiver(); + initiateSettingsObserver(); + } - mSmartPixelsEnable = Settings.System.getIntForUser( - resolver, Settings.System.SMART_PIXELS_ENABLE, - 0, UserHandle.USER_CURRENT); - mSmartPixelsOnPowerSave = Settings.System.getIntForUser( - resolver, Settings.System.SMART_PIXELS_ON_POWER_SAVE, - 0, UserHandle.USER_CURRENT); - mLowPowerMode = Settings.Global.getInt( - resolver, Settings.Global.LOW_POWER_MODE, 0); + private void registerReceiver() { + IntentFilter filter = new IntentFilter(); + filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); + filter.addAction(Intent.ACTION_USER_FOREGROUND); + mContext.registerReceiver(this, filter); + } - if ((mSmartPixelsEnable == 0) && (mSmartPixelsOnPowerSave != 0)) { - if ((mLowPowerMode != 0) && !mSmartPixelsRunning) { - mContext.startService(smartPixels); - mSmartPixelsRunning = true; - Log.d(TAG, "Started Smart Pixels service by LowPowerMode enable"); - } else if ((mLowPowerMode == 0) && mSmartPixelsRunning) { - mContext.stopService(smartPixels); - mSmartPixelsRunning = false; - Log.d(TAG, "Stopped Smart Pixels service by LowPowerMode disable"); - } else if ((mLowPowerMode != 0) && mSmartPixelsRunning) { - mContext.stopService(smartPixels); - mContext.startService(smartPixels); - Log.d(TAG, "Restarted Smart Pixels service by LowPowerMode enable"); - } - } else if ((mSmartPixelsEnable != 0) && !mSmartPixelsRunning) { - mContext.startService(smartPixels); - mSmartPixelsRunning = true; - Log.d(TAG, "Started Smart Pixels service by enable"); - } else if ((mSmartPixelsEnable == 0) && mSmartPixelsRunning) { - mContext.stopService(smartPixels); - mSmartPixelsRunning = false; - Log.d(TAG, "Stopped Smart Pixels service by disable"); - } else if ((mSmartPixelsEnable != 0) && mSmartPixelsRunning) { - mContext.stopService(smartPixels); - mContext.startService(smartPixels); - Log.d(TAG, "Restarted Smart Pixels service"); - } - } - } + private void initiateSettingsObserver() { + mSettingsObserver = new SettingsObserver(mHandler); + mSettingsObserver.observe(); + mSettingsObserver.update(); + } - @Override - public void onReceive(final Context context, Intent intent) { - mContext = context; - try { - if (mSettingsObserver == null) { - mSettingsObserver = new SettingsObserver(mHandler); - mSettingsObserver.observe(); - } - mSettingsObserver.update(); - } catch (Exception e) { - Log.e(TAG, "Can't start load average service", e); - } - } + private class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + void observe() { + mResolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SMART_PIXELS_ENABLE), + false, this, UserHandle.USER_ALL); + mResolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SMART_PIXELS_ON_POWER_SAVE), + false, this, UserHandle.USER_ALL); + mResolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SMART_PIXELS_PATTERN), + false, this, UserHandle.USER_ALL); + mResolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SMART_PIXELS_SHIFT_TIMEOUT), + false, this, UserHandle.USER_ALL); + update(); + } + + @Override + public void onChange(boolean selfChange) { + update(); + } + + public void update() { + mSmartPixelsEnable = (Settings.System.getIntForUser( + mResolver, Settings.System.SMART_PIXELS_ENABLE, + 0, UserHandle.USER_CURRENT) == 1); + mSmartPixelsOnPowerSave = (Settings.System.getIntForUser( + mResolver, Settings.System.SMART_PIXELS_ON_POWER_SAVE, + 0, UserHandle.USER_CURRENT) == 1); + mPowerSaveEnable = mPowerManager.isPowerSaveMode(); + + if (!mSmartPixelsEnable && mSmartPixelsOnPowerSave) { + if (mPowerSaveEnable && !mSmartPixelsRunning) { + mContext.startService(mSmartPixelsService); + mSmartPixelsRunning = true; + Log.d(TAG, "Started Smart Pixels Service by Power Save enable"); + } else if (!mPowerSaveEnable && mSmartPixelsRunning) { + mContext.stopService(mSmartPixelsService); + mSmartPixelsRunning = false; + Log.d(TAG, "Stopped Smart Pixels Service by Power Save disable"); + } else if (mPowerSaveEnable && mSmartPixelsRunning) { + mContext.stopService(mSmartPixelsService); + mContext.startService(mSmartPixelsService); + Log.d(TAG, "Restarted Smart Pixels Service by Power Save enable"); + } + } else if (mSmartPixelsEnable && !mSmartPixelsRunning) { + mContext.startService(mSmartPixelsService); + mSmartPixelsRunning = true; + Log.d(TAG, "Started Smart Pixels Service by enable"); + } else if (!mSmartPixelsEnable && mSmartPixelsRunning) { + mContext.stopService(mSmartPixelsService); + mSmartPixelsRunning = false; + Log.d(TAG, "Stopped Smart Pixels Service by disable"); + } else if (mSmartPixelsEnable && mSmartPixelsRunning) { + mContext.stopService(mSmartPixelsService); + mContext.startService(mSmartPixelsService); + Log.d(TAG, "Restarted Smart Pixels Service"); + } + } + } + + @Override + public void onReceive(final Context context, Intent intent) { + mSettingsObserver.update(); + } } diff --git a/services/core/java/com/android/server/smartpixels/SmartPixelsService.java b/services/core/java/com/android/server/smartpixels/SmartPixelsService.java index f25522ee7f00b..5efe51e5dcc2f 100644 --- a/services/core/java/com/android/server/smartpixels/SmartPixelsService.java +++ b/services/core/java/com/android/server/smartpixels/SmartPixelsService.java @@ -1,6 +1,7 @@ /* * Copyright (c) 2015, Sergii Pylypenko * (c) 2018, Joe Maples + * (c) 2018, Adin Kwok * (c) 2018, CarbonROM * All rights reserved. * @@ -50,6 +51,7 @@ import android.graphics.drawable.BitmapDrawable; import android.os.Handler; import android.os.IBinder; +import android.os.PowerManager; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -67,7 +69,6 @@ public class SmartPixelsService extends Service { private Bitmap bmp; private boolean destroyed = false; - private boolean intentProcessed = false; public static boolean running = false; private int startCounter = 0; @@ -122,18 +123,19 @@ public void startFilter() { return; } - startCounter++; final int handlerStartCounter = startCounter; final Handler handler = new Handler(); + final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); handler.postDelayed(new Runnable() { @Override public void run() { if (view == null || destroyed || handlerStartCounter != startCounter) { return; + } else if (pm.isInteractive()) { + updatePattern(); + view.invalidate(); } - updatePattern(); - view.invalidate(); if (!destroyed) { handler.postDelayed(this, Grids.ShiftTimeouts[mShiftTimeout]); } @@ -154,22 +156,7 @@ public void stopFilter() { @Override public int onStartCommand(Intent intent, int flags, int startId) { - if(intent != null && intent.getAction() != null) { - return START_STICKY; - } - else { - if (Intent.ACTION_DELETE.equals(intent.getAction()) || - (intentProcessed && Intent.ACTION_INSERT.equals(intent.getAction()))) { - Log.d(LOG, "Service got shutdown intent"); - stopSelf(); - intentProcessed = true; - return START_NOT_STICKY; - } - - intentProcessed = true; - Log.d(LOG, "Service got intent " + intent.getAction()); - return START_REDELIVER_INTENT; - } + return START_STICKY; } @Override diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index fe9ad0d044deb..7d9517a22a924 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -104,6 +104,7 @@ import com.android.server.restrictions.RestrictionsManagerService; import com.android.server.security.KeyAttestationApplicationIdProviderService; import com.android.server.security.KeyChainSystemService; +import com.android.server.smartpixels.SmartPixelsReceiver; import com.android.server.soundtrigger.SoundTriggerService; import com.android.server.statusbar.StatusBarManagerService; import com.android.server.storage.DeviceStorageMonitorService; @@ -242,6 +243,7 @@ public final class SystemServer { private PackageManager mPackageManager; private ContentResolver mContentResolver; private EntropyMixer mEntropyMixer; + private SmartPixelsReceiver mSmartPixelsReceiver; private boolean mOnlyCore; private boolean mFirstBoot; @@ -1900,6 +1902,7 @@ private void startOtherServices() { reportWtf("Notifying incident daemon running", e); } traceEnd(); + mSmartPixelsReceiver = new SmartPixelsReceiver(context); }, BOOT_TIMINGS_TRACE_LOG); } From 9e403fa05ae3b1c20811ab3d0b12e28e3ab25d93 Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Thu, 30 Nov 2017 17:41:57 -0800 Subject: [PATCH 28/73] Make sure apps cannot forge package name on AssistStructure used for Autofill. Test: cts-tradefed run commandAndExit cts-dev -m CtsAutoFillServiceTestCases -t android.autofillservice.cts.VirtualContainerActivityTest#testAppCannotFakePackageName Test: cts-tradefed run commandAndExit cts-dev -m CtsAutoFillServiceTestCases Bug: 69981710 Change-Id: Id6036cddb51dd8dd0c9128b7212d573f630d693f Merged-In: Id6036cddb51dd8dd0c9128b7212d573f630d693f (cherry picked from commit 23e61a9086a34405e277868474e003b37ed1b711) --- .../android/app/assist/AssistStructure.java | 10 +++++ .../view/autofill/AutofillManager.java | 26 ++++++++++-- .../view/autofill/IAutoFillManager.aidl | 6 ++- proto/src/metrics_constants.proto | 13 ++++++ .../autofill/AutofillManagerService.java | 14 ++++--- .../autofill/AutofillManagerServiceImpl.java | 40 +++++++++++++++++-- .../com/android/server/autofill/Session.java | 32 ++++++++++----- 7 files changed, 115 insertions(+), 26 deletions(-) diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 9383626360dc4..bf715c35d9b77 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -2058,6 +2058,16 @@ public ComponentName getActivityComponent() { return mActivityComponent; } + /** + * Called by Autofill server when app forged a different value. + * + * @hide + */ + public void setActivityComponent(ComponentName componentName) { + ensureData(); + mActivityComponent = componentName; + } + /** @hide */ public int getFlags() { return mFlags; diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 4fb2a99af5754..ba738b6dfceae 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -24,6 +24,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; +import android.app.Activity; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; @@ -44,6 +46,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.Preconditions; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -390,7 +393,7 @@ boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width, int h * @hide */ public AutofillManager(Context context, IAutoFillManager service) { - mContext = context; + mContext = Preconditions.checkNotNull(context, "context cannot be null"); mService = service; } @@ -940,6 +943,13 @@ private AutofillClient getClientLocked() { return mContext.getAutofillClient(); } + private ComponentName getComponentNameFromContext() { + if (mContext instanceof Activity) { + return ((Activity) mContext).getComponentName(); + } + return null; + } + /** @hide */ public void onAuthenticationResult(int authenticationId, Intent data) { if (!hasAutofillFeature()) { @@ -990,9 +1000,14 @@ private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds, return; } try { + final ComponentName componentName = getComponentNameFromContext(); + if (componentName == null) { + Log.w(TAG, "startSessionLocked(): context is not activity: " + mContext); + return; + } mSessionId = mService.startSession(mContext.getActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), - mCallback != null, flags, mContext.getOpPackageName()); + mCallback != null, flags, componentName); if (mSessionId != NO_SESSION) { mState = STATE_ACTIVE; } @@ -1050,9 +1065,14 @@ private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value try { if (restartIfNecessary) { + final ComponentName componentName = getComponentNameFromContext(); + if (componentName == null) { + Log.w(TAG, "startSessionLocked(): context is not activity: " + mContext); + return; + } final int newId = mService.updateOrRestartSession(mContext.getActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), - mCallback != null, flags, mContext.getOpPackageName(), mSessionId, action); + mCallback != null, flags, componentName, mSessionId, action); if (newId != mSessionId) { if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId); mSessionId = newId; diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl index 6bd9bec368c88..9329c4dcff6a5 100644 --- a/core/java/android/view/autofill/IAutoFillManager.aidl +++ b/core/java/android/view/autofill/IAutoFillManager.aidl @@ -16,6 +16,7 @@ package android.view.autofill; +import android.content.ComponentName; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; @@ -34,14 +35,15 @@ interface IAutoFillManager { int addClient(in IAutoFillManagerClient client, int userId); int startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId, in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags, - String packageName); + in ComponentName componentName); FillEventHistory getFillEventHistory(); boolean restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback); void updateSession(int sessionId, in AutofillId id, in Rect bounds, in AutofillValue value, int action, int flags, int userId); int updateOrRestartSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId, in Rect bounds, in AutofillValue value, int userId, - boolean hasCallback, int flags, String packageName, int sessionId, int action); + boolean hasCallback, int flags, in ComponentName componentName, int sessionId, + int action); void finishSession(int sessionId, int userId); void cancelSession(int sessionId, int userId); void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId); diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index a27515c0266d2..3cadff242e039 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -4006,6 +4006,19 @@ message MetricsEvent { // OS: O FIELD_NOTIFICATION_GROUP_SUMMARY = 947; + // An app attempted to forge a different component name in the AssisStructure that would be + // passed to the autofill service. + // OS: O (security patch) + // Package: Real package of the app being autofilled + // Tag FIELD_AUTOFILL_SERVICE: Package of the autofill service that processed the request + // TAG FIELD_AUTOFILL_FORGED_COMPONENT_NAME: Component name being forged + AUTOFILL_FORGED_COMPONENT_ATTEMPT = 948; + + // FIELD - The component that an app tried tro forged. + // Type: string + // OS: O (security patch) + FIELD_AUTOFILL_FORGED_COMPONENT_NAME = 949; + // ---- End O Constants, all O constants go above this line ---- // OPEN: Settings > System > Languages & input > Advanced > Lift to open camera diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 1f4161ac54d4c..ffc778a8ee405 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -533,25 +533,26 @@ public void setHasCallback(int sessionId, int userId, boolean hasIt) { @Override public int startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId, Rect bounds, AutofillValue value, int userId, boolean hasCallback, int flags, - String packageName) { + ComponentName componentName) { activityToken = Preconditions.checkNotNull(activityToken, "activityToken"); appCallback = Preconditions.checkNotNull(appCallback, "appCallback"); autofillId = Preconditions.checkNotNull(autofillId, "autoFillId"); - packageName = Preconditions.checkNotNull(packageName, "packageName"); + componentName = Preconditions.checkNotNull(componentName, "componentName"); + final String packageName = Preconditions.checkNotNull(componentName.getPackageName()); Preconditions.checkArgument(userId == UserHandle.getUserId(getCallingUid()), "userId"); try { mContext.getPackageManager().getPackageInfoAsUser(packageName, 0, userId); } catch (PackageManager.NameNotFoundException e) { - throw new IllegalArgumentException(packageName + " is not a valid package", e); + throw new IllegalArgumentException(componentName + " is not a valid package", e); } synchronized (mLock) { final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); return service.startSessionLocked(activityToken, getCallingUid(), appCallback, - autofillId, bounds, value, hasCallback, flags, packageName); + autofillId, bounds, value, hasCallback, flags, componentName); } } @@ -603,7 +604,8 @@ public void updateSession(int sessionId, AutofillId autoFillId, Rect bounds, @Override public int updateOrRestartSession(IBinder activityToken, IBinder appCallback, AutofillId autoFillId, Rect bounds, AutofillValue value, int userId, - boolean hasCallback, int flags, String packageName, int sessionId, int action) { + boolean hasCallback, int flags, ComponentName componentName, int sessionId, + int action) { boolean restart = false; synchronized (mLock) { final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); @@ -614,7 +616,7 @@ public int updateOrRestartSession(IBinder activityToken, IBinder appCallback, } if (restart) { return startSession(activityToken, appCallback, autoFillId, bounds, value, userId, - hasCallback, flags, packageName); + hasCallback, flags, componentName); } // Nothing changed... diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 3a3b5707fc6f1..a17c3ca92e5dd 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -32,6 +32,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ServiceInfo; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -43,6 +44,7 @@ import android.os.Looper; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.service.autofill.AutofillService; @@ -279,7 +281,7 @@ void setHasCallback(int sessionId, int uid, boolean hasIt) { int startSessionLocked(@NonNull IBinder activityToken, int uid, @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback, - int flags, @NonNull String packageName) { + int flags, @NonNull ComponentName componentName) { if (!isEnabled()) { return 0; } @@ -289,7 +291,7 @@ int startSessionLocked(@NonNull IBinder activityToken, int uid, pruneAbandonedSessionsLocked(); final Session newSession = createSessionByTokenLocked(activityToken, uid, appCallbackToken, - hasCallback, packageName); + hasCallback, componentName); if (newSession == null) { return NO_SESSION; } @@ -386,7 +388,8 @@ void disableOwnedAutofillServicesLocked(int uid) { } private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid, - @NonNull IBinder appCallbackToken, boolean hasCallback, @NonNull String packageName) { + @NonNull IBinder appCallbackToken, boolean hasCallback, + @NonNull ComponentName componentName) { // use random ids so that one app cannot know that another app creates sessions int sessionId; int tries = 0; @@ -400,14 +403,43 @@ private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int u sessionId = sRandom.nextInt(); } while (sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0); + assertCallerLocked(componentName); + final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock, sessionId, uid, activityToken, appCallbackToken, hasCallback, - mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), packageName); + mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), componentName); mSessions.put(newSession.id, newSession); return newSession; } + /** + * Asserts the component is owned by the caller. + */ + private void assertCallerLocked(@NonNull ComponentName componentName) { + final PackageManager pm = mContext.getPackageManager(); + final int callingUid = Binder.getCallingUid(); + final int packageUid; + try { + packageUid = pm.getPackageUidAsUser(componentName.getPackageName(), + UserHandle.getCallingUserId()); + } catch (NameNotFoundException e) { + throw new SecurityException("Could not verify UID for " + componentName); + } + if (callingUid != packageUid) { + final String[] packages = pm.getPackagesForUid(callingUid); + final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid; + Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid + + ") passed component (" + componentName + ") owned by UID " + packageUid); + mMetricsLogger.write(new LogMaker(MetricsEvent.AUTOFILL_FORGED_COMPONENT_ATTEMPT) + .setPackageName(callingPackage) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, getServicePackageName()) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FORGED_COMPONENT_NAME, + componentName == null ? "null" : componentName.flattenToShortString())); + throw new SecurityException("Invalid component: " + componentName); + } + } + /** * Restores a session after an activity was temporarily destroyed. * diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index de2950dd5985f..1f0e51c694aaa 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -126,8 +126,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") @NonNull private IBinder mActivityToken; - /** Package name of the app that is auto-filled */ - @NonNull private final String mPackageName; + /** Component that's being auto-filled */ + @NonNull private final ComponentName mComponentName; @GuardedBy("mLock") private final ArrayMap mViewStates = new ArrayMap<>(); @@ -227,6 +227,16 @@ public void send(int resultCode, Bundle resultData) throws RemoteException { structure.ensureData(); // Sanitize structure before it's sent to service. + final ComponentName componentNameFromApp = structure.getActivityComponent(); + if (!mComponentName.equals(componentNameFromApp)) { + Slog.w(TAG, "Activity " + mComponentName + " forged different component on " + + "AssistStructure: " + componentNameFromApp); + structure.setActivityComponent(mComponentName); + mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_FORGED_COMPONENT_ATTEMPT) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FORGED_COMPONENT_NAME, + componentNameFromApp == null ? "null" + : componentNameFromApp.flattenToShortString())); + } structure.sanitizeForParceling(true); // Flags used to start the session. @@ -415,7 +425,7 @@ private void requestNewFillResponseLocked(int flags) { @NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId, @NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken, @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory, - @NonNull ComponentName componentName, @NonNull String packageName) { + @NonNull ComponentName serviceComponentName, @NonNull ComponentName appComponentName) { id = sessionId; this.uid = uid; mStartTime = SystemClock.elapsedRealtime(); @@ -423,11 +433,11 @@ private void requestNewFillResponseLocked(int flags) { mLock = lock; mUi = ui; mHandlerCaller = handlerCaller; - mRemoteFillService = new RemoteFillService(context, componentName, userId, this); + mRemoteFillService = new RemoteFillService(context, serviceComponentName, userId, this); mActivityToken = activityToken; mHasCallback = hasCallback; mUiLatencyHistory = uiLatencyHistory; - mPackageName = packageName; + mComponentName = appComponentName; mClient = IAutoFillManagerClient.Stub.asInterface(client); writeLog(MetricsEvent.AUTOFILL_SESSION_STARTED); @@ -1008,8 +1018,8 @@ public boolean showSaveLocked() { final IAutoFillManagerClient client = getClient(); mPendingSaveUi = new PendingUi(mActivityToken, id, client); getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(), - mService.getServicePackageName(), saveInfo, valueFinder, mPackageName, this, - mPendingSaveUi); + mService.getServicePackageName(), saveInfo, valueFinder, + mComponentName.getPackageName(), this, mPendingSaveUi); if (client != null) { try { client.setSaveUiState(id, true); @@ -1365,7 +1375,7 @@ public void onFillReady(FillResponse response, AutofillId filledId, } getUiForShowing().showFillUi(filledId, response, filterText, - mService.getServicePackageName(), mPackageName, this); + mService.getServicePackageName(), mComponentName.getPackageName(), this); synchronized (mLock) { if (mUiShownTime == 0) { @@ -1690,14 +1700,14 @@ private void startAuthentication(int authenticationId, IntentSender intent, @Override public String toString() { - return "Session: [id=" + id + ", pkg=" + mPackageName + "]"; + return "Session: [id=" + id + ", pkg=" + mComponentName.getPackageName() + "]"; } void dumpLocked(String prefix, PrintWriter pw) { final String prefix2 = prefix + " "; pw.print(prefix); pw.print("id: "); pw.println(id); pw.print(prefix); pw.print("uid: "); pw.println(uid); - pw.print(prefix); pw.print("mPackagename: "); pw.println(mPackageName); + pw.print(prefix); pw.print("mComponentName: "); pw.println(mComponentName); pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken); pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime); pw.print(prefix); pw.print("Time to show UI: "); @@ -1920,7 +1930,7 @@ private LogMaker newLogMaker(int category) { } private LogMaker newLogMaker(int category, String servicePackageName) { - return Helper.newLogMaker(category, mPackageName, servicePackageName); + return Helper.newLogMaker(category, mComponentName.getPackageName(), servicePackageName); } private void writeLog(int category) { From bdfe972294dd6c1ba2fbfb071c6b943de03cef50 Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Mon, 11 Dec 2017 14:37:35 -0800 Subject: [PATCH 29/73] Proper autofill fix to let phone process autofill Settings activity. Test: adb shell am start com.android.settings/.RadioInfo Bug: 69981710 Fixes: 70506888 Change-Id: Id29bad2d20b621f7379eb6144c95dcc819949b3d Merged-In: Id29bad2d20b621f7379eb6144c95dcc819949b3d (cherry picked from commit 97f16a76db29269619d9a1b45d4cea49026a5b6a) (cherry picked from commit 92b5d2783a1b97bee476f04754481403839b4e45) --- .../android/app/ActivityManagerInternal.java | 5 +++++ .../autofill/AutofillManagerServiceImpl.java | 9 ++++++--- .../server/am/ActivityManagerService.java | 20 +++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index c8d983933fc6d..9dceb7f9e4339 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -268,4 +268,9 @@ public abstract int startActivitiesAsPackage(String packageName, * @param token The IApplicationToken for the activity */ public abstract void setFocusedActivity(IBinder token); + + /** + * Returns {@code true} if {@code uid} is running an activity from {@code packageName}. + */ + public abstract boolean hasRunningActivity(int uid, @Nullable String packageName); } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index a17c3ca92e5dd..6d3398ea2648b 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -26,6 +26,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.IActivityManager; import android.content.ComponentName; @@ -69,6 +70,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.HandlerCaller; +import com.android.server.LocalServices; import com.android.server.autofill.ui.AutoFillUI; import java.io.PrintWriter; @@ -417,16 +419,17 @@ private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int u * Asserts the component is owned by the caller. */ private void assertCallerLocked(@NonNull ComponentName componentName) { + final String packageName = componentName.getPackageName(); final PackageManager pm = mContext.getPackageManager(); final int callingUid = Binder.getCallingUid(); final int packageUid; try { - packageUid = pm.getPackageUidAsUser(componentName.getPackageName(), - UserHandle.getCallingUserId()); + packageUid = pm.getPackageUidAsUser(packageName, UserHandle.getCallingUserId()); } catch (NameNotFoundException e) { throw new SecurityException("Could not verify UID for " + componentName); } - if (callingUid != packageUid) { + if (callingUid != packageUid && !LocalServices.getService(ActivityManagerInternal.class) + .hasRunningActivity(callingUid, packageName)) { final String[] packages = pm.getPackagesForUid(callingUid); final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid; Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 1520b96a4afd4..4a2d9c9ca5a48 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -24248,6 +24248,26 @@ public void setFocusedActivity(IBinder token) { } } } + + @Override + public boolean hasRunningActivity(int uid, @Nullable String packageName) { + if (packageName == null) return false; + + synchronized (ActivityManagerService.this) { + for (int i = 0; i < mLruProcesses.size(); i++) { + final ProcessRecord processRecord = mLruProcesses.get(i); + if (processRecord.uid == uid) { + for (int j = 0; j < processRecord.activities.size(); j++) { + final ActivityRecord activityRecord = processRecord.activities.get(j); + if (packageName.equals(activityRecord.packageName)) { + return true; + } + } + } + } + } + return false; + } } /** From fbbc3fb97bd03dea44ab1848a495e8e7aa4d3b4d Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Mon, 22 Jan 2018 15:33:40 -0800 Subject: [PATCH 30/73] DO NOT MERGE - fix AFM.getComponentNameFromContext() This method broke on O-MR1 when I3abf999eb6056c1df7982780bae43b58337c0668 was chery-picked from master. Test: cts-tradefed run commandAndExit cts-dev -m CtsAutoFillServiceTestCases \ -t android.autofillservice.cts.AttachedContextActivityTest#testAutofill Test: cts-tradefed run commandAndExit cts-dev -m CtsAutoFillServiceTestCases Also individually ran tests that failed (due to flakiness) when ran in a suite: Test: cts-tradefed run commandAndExit cts-dev -m CtsAutoFillServiceTestCases \ -t android.autofillservice.cts.OptionalSaveActivityTest#testDontShowSaveUiWhenUserManuallyFilled_oneDatasetAllRequiredFields Test: cts-tradefed run commandAndExit cts-dev -m CtsAutoFillServiceTestCases -t android.autofillservice.cts.PreSimpleSaveActivityTest #testTapLink_tapBack_thenStartOverBySayingYesAndManualRequest Fixes: 71960322 Change-Id: Ia093dcefe6699dc9493c46d671e48c2000214b31 Merged-In: I3abf999eb6056c1df7982780bae43b58337c0668 (cherry picked from commit b25b4736db2ea05dce6b9f03e8a9920ab338dd5a) --- core/java/android/app/Activity.java | 10 ++++++++ .../view/autofill/AutofillManager.java | 23 +++++++++++-------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 2c04f8ff95485..8dc558ccf7552 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5871,6 +5871,16 @@ public ComponentName getComponentName() return mComponent; } + /** + * Temporary method on O-MR1 only. + * + * @hide + */ + @Override + public ComponentName getComponentNameForAutofill() { + return mComponent; + } + /** * Retrieve a {@link SharedPreferences} object for accessing preferences * that are private to this activity. This simply calls the underlying diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index ba738b6dfceae..fb9534b2e52e2 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -24,7 +24,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; -import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -387,6 +386,13 @@ boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width, int h * Runs the specified action on the UI thread. */ void runOnUiThread(Runnable action); + + /** + * Gets the complete component name of this client. + * + *

Temporary method on O-MR1 only. + */ + ComponentName getComponentNameForAutofill(); } /** @@ -943,11 +949,8 @@ private AutofillClient getClientLocked() { return mContext.getAutofillClient(); } - private ComponentName getComponentNameFromContext() { - if (mContext instanceof Activity) { - return ((Activity) mContext).getComponentName(); - } - return null; + private ComponentName getComponentNameFromContext(AutofillClient client) { + return client == null ? null : client.getComponentNameForAutofill(); } /** @hide */ @@ -1000,7 +1003,8 @@ private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds, return; } try { - final ComponentName componentName = getComponentNameFromContext(); + final AutofillClient client = getClientLocked(); + final ComponentName componentName = getComponentNameFromContext(client); if (componentName == null) { Log.w(TAG, "startSessionLocked(): context is not activity: " + mContext); return; @@ -1011,7 +1015,6 @@ private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds, if (mSessionId != NO_SESSION) { mState = STATE_ACTIVE; } - final AutofillClient client = getClientLocked(); if (client != null) { client.autofillCallbackResetableStateAvailable(); } @@ -1065,7 +1068,8 @@ private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value try { if (restartIfNecessary) { - final ComponentName componentName = getComponentNameFromContext(); + final AutofillClient client = getClientLocked(); + final ComponentName componentName = getComponentNameFromContext(client); if (componentName == null) { Log.w(TAG, "startSessionLocked(): context is not activity: " + mContext); return; @@ -1077,7 +1081,6 @@ private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId); mSessionId = newId; mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE; - final AutofillClient client = getClientLocked(); if (client != null) { client.autofillCallbackResetableStateAvailable(); } From 4f2587a287226bfd7d1e564062a4869c9ff8720b Mon Sep 17 00:00:00 2001 From: Marco Nelissen Date: Tue, 7 Nov 2017 13:52:02 -0800 Subject: [PATCH 31/73] Rework thumbnail cleanup Bug: 63766886 Test: ran CTS tests Change-Id: I1f92bb014e275eafe3f42aef1f8c817f187c6608 (cherry picked from commit 6d2096f3889d38da60099b1b5678347de4f042bf) --- core/java/android/provider/MediaStore.java | 4 +- media/java/android/media/MediaScanner.java | 52 -------------------- media/java/android/media/MiniThumbFile.java | 54 ++++++++++++++++++++- 3 files changed, 54 insertions(+), 56 deletions(-) diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 13e1e26b51c33..ff4f358529a41 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -694,8 +694,8 @@ static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, int ki // Log.v(TAG, "getThumbnail: origId="+origId+", kind="+kind+", isVideo="+isVideo); // If the magic is non-zero, we simply return thumbnail if it does exist. // querying MediaProvider and simply return thumbnail. - MiniThumbFile thumbFile = new MiniThumbFile(isVideo ? Video.Media.EXTERNAL_CONTENT_URI - : Images.Media.EXTERNAL_CONTENT_URI); + MiniThumbFile thumbFile = MiniThumbFile.instance( + isVideo ? Video.Media.EXTERNAL_CONTENT_URI : Images.Media.EXTERNAL_CONTENT_URI); Cursor c = null; try { long magic = thumbFile.getMagic(origId); diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index cb4e46fe945a3..e66945bd7ec46 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -323,7 +323,6 @@ public class MediaScanner implements AutoCloseable { private final Uri mAudioUri; private final Uri mVideoUri; private final Uri mImagesUri; - private final Uri mThumbsUri; private final Uri mPlaylistsUri; private final Uri mFilesUri; private final Uri mFilesUriNoNotify; @@ -419,7 +418,6 @@ public MediaScanner(Context c, String volumeName) { mAudioUri = Audio.Media.getContentUri(volumeName); mVideoUri = Video.Media.getContentUri(volumeName); mImagesUri = Images.Media.getContentUri(volumeName); - mThumbsUri = Images.Thumbnails.getContentUri(volumeName); mFilesUri = Files.getContentUri(volumeName); mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build(); @@ -1283,53 +1281,6 @@ private void prescan(String filePath, boolean prescanFiles) throws RemoteExcepti } } - private void pruneDeadThumbnailFiles() { - HashSet existingFiles = new HashSet(); - String directory = "/sdcard/DCIM/.thumbnails"; - String [] files = (new File(directory)).list(); - Cursor c = null; - if (files == null) - files = new String[0]; - - for (int i = 0; i < files.length; i++) { - String fullPathString = directory + "/" + files[i]; - existingFiles.add(fullPathString); - } - - try { - c = mMediaProvider.query( - mThumbsUri, - new String [] { "_data" }, - null, - null, - null, null); - Log.v(TAG, "pruneDeadThumbnailFiles... " + c); - if (c != null && c.moveToFirst()) { - do { - String fullPathString = c.getString(0); - existingFiles.remove(fullPathString); - } while (c.moveToNext()); - } - - for (String fileToDelete : existingFiles) { - if (false) - Log.v(TAG, "fileToDelete is " + fileToDelete); - try { - (new File(fileToDelete)).delete(); - } catch (SecurityException ex) { - } - } - - Log.v(TAG, "/pruneDeadThumbnailFiles... " + c); - } catch (RemoteException e) { - // We will soon be killed... - } finally { - if (c != null) { - c.close(); - } - } - } - static class MediaBulkDeleter { StringBuilder whereClause = new StringBuilder(); ArrayList whereArgs = new ArrayList(100); @@ -1373,9 +1324,6 @@ private void postscan(final String[] directories) throws RemoteException { processPlayLists(); } - if (mOriginalCount == 0 && mImagesUri.equals(Images.Media.getContentUri("external"))) - pruneDeadThumbnailFiles(); - // allow GC to clean up mPlayLists.clear(); } diff --git a/media/java/android/media/MiniThumbFile.java b/media/java/android/media/MiniThumbFile.java index 664308c45bf9b..98993676ce43d 100644 --- a/media/java/android/media/MiniThumbFile.java +++ b/media/java/android/media/MiniThumbFile.java @@ -44,13 +44,14 @@ */ public class MiniThumbFile { private static final String TAG = "MiniThumbFile"; - private static final int MINI_THUMB_DATA_FILE_VERSION = 3; + private static final int MINI_THUMB_DATA_FILE_VERSION = 4; public static final int BYTES_PER_MINTHUMB = 10000; private static final int HEADER_SIZE = 1 + 8 + 4; private Uri mUri; private RandomAccessFile mMiniThumbFile; private FileChannel mChannel; private ByteBuffer mBuffer; + private ByteBuffer mEmptyBuffer; private static final Hashtable sThumbFiles = new Hashtable(); @@ -127,9 +128,10 @@ private RandomAccessFile miniThumbDataFile() { return mMiniThumbFile; } - public MiniThumbFile(Uri uri) { + private MiniThumbFile(Uri uri) { mUri = uri; mBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB); + mEmptyBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB); } public synchronized void deactivate() { @@ -184,6 +186,54 @@ public synchronized long getMagic(long id) { return 0; } + public synchronized void eraseMiniThumb(long id) { + RandomAccessFile r = miniThumbDataFile(); + if (r != null) { + long pos = id * BYTES_PER_MINTHUMB; + FileLock lock = null; + try { + mBuffer.clear(); + mBuffer.limit(1 + 8); + + lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, false); + // check that we can read the following 9 bytes + // (1 for the "status" and 8 for the long) + if (mChannel.read(mBuffer, pos) == 9) { + mBuffer.position(0); + if (mBuffer.get() == 1) { + long currentMagic = mBuffer.getLong(); + if (currentMagic == 0) { + // there is no thumbnail stored here + Log.i(TAG, "no thumbnail for id " + id); + return; + } + // zero out the thumbnail slot + // Log.v(TAG, "clearing slot " + id + ", magic " + currentMagic + // + " at offset " + pos); + mChannel.write(mEmptyBuffer, pos); + } + } else { + // Log.v(TAG, "No slot"); + } + } catch (IOException ex) { + Log.v(TAG, "Got exception checking file magic: ", ex); + } catch (RuntimeException ex) { + // Other NIO related exception like disk full, read only channel..etc + Log.e(TAG, "Got exception when reading magic, id = " + id + + ", disk full or mount read-only? " + ex.getClass()); + } finally { + try { + if (lock != null) lock.release(); + } + catch (IOException ex) { + // ignore it. + } + } + } else { + // Log.v(TAG, "No data file"); + } + } + public synchronized void saveMiniThumbToFile(byte[] data, long id, long magic) throws IOException { RandomAccessFile r = miniThumbDataFile(); From c1db43a6440d9d6ff61248394603052b1ceb1a0a Mon Sep 17 00:00:00 2001 From: Fyodor Kupolov Date: Fri, 16 Mar 2018 12:20:40 -0700 Subject: [PATCH 32/73] Use concrete CREATOR instance for parceling lists Replaced readTypedArrayList/writeTypedArrayList with writeTypedList/createTypedArrayList(CREATOR) Bug: 71508348 Test: CtsAutoFillServiceTestCases pass Merged-In: I2a8321023b40cc74b7026eb0fb32a9cc5f5543a9 Change-Id: Id17d02e40a4ae567bf2d74d2ea8ba4d8a943bdb7 (cherry picked from commit 4921986db76b1580bcb6ec8b2fd381d1364a6325) --- core/java/android/os/Parcel.java | 15 ++++++++------- core/java/android/service/autofill/Dataset.java | 9 +++++---- .../android/service/autofill/SaveRequest.java | 8 ++++---- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index fae9d5310f8ed..c6d3860c66006 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -1340,6 +1340,13 @@ public final void readBinderArray(IBinder[] val) { * @see Parcelable */ public final void writeTypedList(List val) { + writeTypedList(val, 0); + } + + /** + * @hide + */ + public void writeTypedList(List val, int parcelableFlags) { if (val == null) { writeInt(-1); return; @@ -1348,13 +1355,7 @@ public final void writeTypedList(List val) { int i=0; writeInt(N); while (i < N) { - T item = val.get(i); - if (item != null) { - writeInt(1); - item.writeToParcel(this, 0); - } else { - writeInt(0); - } + writeTypedObject(val.get(i), parcelableFlags); i++; } } diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index 65b0efcbe032a..21a3df89e0f83 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -316,8 +316,8 @@ public int describeContents() { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeParcelable(mPresentation, flags); - parcel.writeTypedArrayList(mFieldIds, flags); - parcel.writeTypedArrayList(mFieldValues, flags); + parcel.writeTypedList(mFieldIds, flags); + parcel.writeTypedList(mFieldValues, flags); parcel.writeParcelableList(mFieldPresentations, flags); parcel.writeParcelable(mAuthentication, flags); parcel.writeString(mId); @@ -333,8 +333,9 @@ public Dataset createFromParcel(Parcel parcel) { final Builder builder = (presentation == null) ? new Builder() : new Builder(presentation); - final ArrayList ids = parcel.readTypedArrayList(null); - final ArrayList values = parcel.readTypedArrayList(null); + final ArrayList ids = parcel.createTypedArrayList(AutofillId.CREATOR); + final ArrayList values = + parcel.createTypedArrayList(AutofillValue.CREATOR); final ArrayList presentations = new ArrayList<>(); parcel.readParcelableList(presentations, null); final int idCount = (ids != null) ? ids.size() : 0; diff --git a/core/java/android/service/autofill/SaveRequest.java b/core/java/android/service/autofill/SaveRequest.java index 9de931542cb91..fc4272dd7742c 100644 --- a/core/java/android/service/autofill/SaveRequest.java +++ b/core/java/android/service/autofill/SaveRequest.java @@ -19,9 +19,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; -import android.os.CancellationSignal; import android.os.Parcel; import android.os.Parcelable; + import com.android.internal.util.Preconditions; import java.util.ArrayList; @@ -45,7 +45,7 @@ public SaveRequest(@NonNull ArrayList fillContexts, } private SaveRequest(@NonNull Parcel parcel) { - this(parcel.readTypedArrayList(null), parcel.readBundle()); + this(parcel.createTypedArrayList(FillContext.CREATOR), parcel.readBundle()); } /** @@ -57,7 +57,7 @@ private SaveRequest(@NonNull Parcel parcel) { /** * Gets the extra client state returned from the last {@link - * AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)} + * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)} * fill request}. * * @return The client state. @@ -73,7 +73,7 @@ public int describeContents() { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeTypedArrayList(mFillContexts, flags); + parcel.writeTypedList(mFillContexts, flags); parcel.writeBundle(mClientState); } From 0da68f30a9262a74c47a6c12f99d848320c8aefc Mon Sep 17 00:00:00 2001 From: akirilov Date: Tue, 27 Mar 2018 13:08:47 -0700 Subject: [PATCH 33/73] RESTRICT AUTOMERGE: Prevent reporting fake package name - framework (backport to oc-mr1-dev) Test: added AccessibilityEndToEndTest#testPackageNameCannotBeFaked cts-tradefed run cts -m CtsAccessibilityServiceTestCases cts-tradefed run cts -m CtsAccessibilityTestCases Bug: 69981755 Change-Id: If3752e106aa7fdee4645dc9852289af471ceff18 Merged-In: I13304efbee10d1affa087e9c8bc4ec237643283e (cherry picked from commit c36db6d473c9988496cd614924ee113b67f7e333) --- .../IAccessibilityServiceConnection.aidl | 10 +- .../appwidget/AppWidgetManagerInternal.java | 36 +++ core/java/android/view/ViewRootImpl.java | 1 + .../AccessibilityInteractionClient.java | 115 ++++--- .../accessibility/AccessibilityManager.java | 5 +- .../accessibility/IAccessibilityManager.aidl | 2 +- .../AccessibilityManagerService.java | 288 +++++++++++++----- .../appwidget/AppWidgetServiceImpl.java | 25 ++ 8 files changed, 360 insertions(+), 122 deletions(-) create mode 100644 core/java/android/appwidget/AppWidgetManagerInternal.java diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 7a1931718888c..037aeb058f15c 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -35,23 +35,23 @@ interface IAccessibilityServiceConnection { void setServiceInfo(in AccessibilityServiceInfo info); - boolean findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, + String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, long threadId, in Bundle arguments); - boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId, + String[] findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); - boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId, + String[] findAccessibilityNodeInfosByViewId(int accessibilityWindowId, long accessibilityNodeId, String viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); - boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType, + String[] findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); - boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction, + String[] focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId, diff --git a/core/java/android/appwidget/AppWidgetManagerInternal.java b/core/java/android/appwidget/AppWidgetManagerInternal.java new file mode 100644 index 0000000000000..5562c550fe188 --- /dev/null +++ b/core/java/android/appwidget/AppWidgetManagerInternal.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 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.appwidget; + +import android.annotation.Nullable; +import android.util.ArraySet; + +/** + * App widget manager local system service interface. + * + * @hide Only for use within the system server. + */ +public abstract class AppWidgetManagerInternal { + + /** + * Gets the packages from which the uid hosts widgets. + * + * @param uid The potential host UID. + * @return Whether the UID hosts widgets from the package. + */ + public abstract @Nullable ArraySet getHostedWidgetPackages(int uid); +} diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 8f250a9e9f15b..3f1ea34c37ab0 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -7733,6 +7733,7 @@ public void ensureConnection() { if (!registered) { mAttachInfo.mAccessibilityWindowId = mAccessibilityManager.addAccessibilityInteractionConnection(mWindow, + mContext.getPackageName(), new AccessibilityInteractionConnection(ViewRootImpl.this)); } } diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 19213ca06c5ec..be3b34d0ccf8a 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -28,6 +28,8 @@ import android.util.LongSparseArray; import android.util.SparseArray; +import com.android.internal.util.ArrayUtils; + import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -283,14 +285,19 @@ public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int conn } final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId( - accessibilityWindowId, accessibilityNodeId, interactionId, this, - prefetchFlags, Thread.currentThread().getId(), arguments); - Binder.restoreCallingIdentity(identityToken); - if (success) { + final String[] packageNames; + try { + packageNames = connection.findAccessibilityNodeInfoByAccessibilityId( + accessibilityWindowId, accessibilityNodeId, interactionId, this, + prefetchFlags, Thread.currentThread().getId(), arguments); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + if (packageNames != null) { List infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, + bypassCache, packageNames); if (infos != null && !infos.isEmpty()) { for (int i = 1; i < infos.size(); i++) { infos.get(i).recycle(); @@ -333,15 +340,21 @@ public List findAccessibilityNodeInfosByViewId(int connec if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success = connection.findAccessibilityNodeInfosByViewId( - accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this, - Thread.currentThread().getId()); - Binder.restoreCallingIdentity(identityToken); - if (success) { + final String[] packageNames; + try { + packageNames = connection.findAccessibilityNodeInfosByViewId( + accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this, + Thread.currentThread().getId()); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + + if (packageNames != null) { List infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); if (infos != null) { - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, + false, packageNames); return infos; } } @@ -381,15 +394,21 @@ public List findAccessibilityNodeInfosByText(int connecti if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success = connection.findAccessibilityNodeInfosByText( - accessibilityWindowId, accessibilityNodeId, text, interactionId, this, - Thread.currentThread().getId()); - Binder.restoreCallingIdentity(identityToken); - if (success) { + final String[] packageNames; + try { + packageNames = connection.findAccessibilityNodeInfosByText( + accessibilityWindowId, accessibilityNodeId, text, interactionId, this, + Thread.currentThread().getId()); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + + if (packageNames != null) { List infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); if (infos != null) { - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, + false, packageNames); return infos; } } @@ -428,14 +447,19 @@ public AccessibilityNodeInfo findFocus(int connectionId, int accessibilityWindow if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success = connection.findFocus(accessibilityWindowId, - accessibilityNodeId, focusType, interactionId, this, - Thread.currentThread().getId()); - Binder.restoreCallingIdentity(identityToken); - if (success) { + final String[] packageNames; + try { + packageNames = connection.findFocus(accessibilityWindowId, + accessibilityNodeId, focusType, interactionId, this, + Thread.currentThread().getId()); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + + if (packageNames != null) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false, packageNames); return info; } } else { @@ -472,14 +496,19 @@ public AccessibilityNodeInfo focusSearch(int connectionId, int accessibilityWind if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success = connection.focusSearch(accessibilityWindowId, - accessibilityNodeId, direction, interactionId, this, - Thread.currentThread().getId()); - Binder.restoreCallingIdentity(identityToken); - if (success) { + final String[] packageNames; + try { + packageNames = connection.focusSearch(accessibilityWindowId, + accessibilityNodeId, direction, interactionId, this, + Thread.currentThread().getId()); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + + if (packageNames != null) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false, packageNames); return info; } } else { @@ -580,7 +609,7 @@ private List getFindAccessibilityNodeInfosResultAndClear( int interactionId) { synchronized (mInstanceLock) { final boolean success = waitForResultTimedLocked(interactionId); - List result = null; + final List result; if (success) { result = mFindAccessibilityNodeInfosResult; } else { @@ -696,13 +725,25 @@ private boolean waitForResultTimedLocked(int interactionId) { * * @param info The info. * @param connectionId The id of the connection to the system. + * @param bypassCache Whether or not to bypass the cache. The node is added to the cache if + * this value is {@code false} + * @param packageNames The valid package names a node can come from. */ private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, - int connectionId) { + int connectionId, boolean bypassCache, String[] packageNames) { if (info != null) { info.setConnectionId(connectionId); + // Empty array means any package name is Okay + if (!ArrayUtils.isEmpty(packageNames) + && !ArrayUtils.contains(packageNames, info.getPackageName().toString())) { + // If the node package not one of the valid ones, pick the top one - this + // is one of the packages running in the introspected UID. + info.setPackageName(packageNames[0]); + } info.setSealed(true); - sAccessibilityCache.add(info); + if (!bypassCache) { + sAccessibilityCache.add(info); + } } } @@ -711,14 +752,18 @@ private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, * * @param infos The {@link AccessibilityNodeInfo}s. * @param connectionId The id of the connection to the system. + * @param bypassCache Whether or not to bypass the cache. The nodes are added to the cache if + * this value is {@code false} + * @param packageNames The valid package names a node can come from. */ private void finalizeAndCacheAccessibilityNodeInfos(List infos, - int connectionId) { + int connectionId, boolean bypassCache, String[] packageNames) { if (infos != null) { final int infosCount = infos.size(); for (int i = 0; i < infosCount; i++) { AccessibilityNodeInfo info = infos.get(i); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, + bypassCache, packageNames); } } } diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 0b9bc5760fa86..2478281efcd17 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -885,7 +885,7 @@ public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName( * @hide */ public int addAccessibilityInteractionConnection(IWindow windowToken, - IAccessibilityInteractionConnection connection) { + String packageName, IAccessibilityInteractionConnection connection) { final IAccessibilityManager service; final int userId; synchronized (mLock) { @@ -896,7 +896,8 @@ public int addAccessibilityInteractionConnection(IWindow windowToken, userId = mUserId; } try { - return service.addAccessibilityInteractionConnection(windowToken, connection, userId); + return service.addAccessibilityInteractionConnection(windowToken, connection, + packageName, userId); } catch (RemoteException re) { Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); } diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 3f499abd2e4d1..6329c1141bd59 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -45,7 +45,7 @@ interface IAccessibilityManager { List getEnabledAccessibilityServiceList(int feedbackType, int userId); int addAccessibilityInteractionConnection(IWindow windowToken, - in IAccessibilityInteractionConnection connection, int userId); + in IAccessibilityInteractionConnection connection, String packageName, int userId); void removeAccessibilityInteractionConnection(IWindow windowToken); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 83dfccb57ebf3..d0d65d94fb5b6 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -34,6 +34,7 @@ import android.app.PendingIntent; import android.app.StatusBarManager; import android.app.UiAutomation; +import android.appwidget.AppWidgetManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -82,6 +83,7 @@ import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; +import android.util.ArraySet; import android.view.Display; import android.view.IWindow; import android.view.InputDevice; @@ -110,10 +112,11 @@ import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.internal.util.IntPair; +import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; import com.android.server.policy.AccessibilityShortcutController; import com.android.server.statusbar.StatusBarManagerInternal; - +import libcore.util.EmptyArray; import org.xmlpull.v1.XmlPullParserException; import java.io.FileDescriptor; @@ -199,6 +202,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private final WindowManagerInternal mWindowManagerService; + private AppWidgetManagerInternal mAppWidgetService; + private final SecurityPolicy mSecurityPolicy; private final MainHandler mMainHandler; @@ -229,10 +234,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private final RemoteCallbackList mGlobalClients = new RemoteCallbackList<>(); - private final SparseArray mGlobalInteractionConnections = + private final SparseArray mGlobalInteractionConnections = new SparseArray<>(); - private AccessibilityConnectionWrapper mPictureInPictureActionReplacingConnection; + private RemoteAccessibilityConnection mPictureInPictureActionReplacingConnection; private final SparseArray mGlobalWindowTokens = new SparseArray<>(); @@ -501,6 +506,11 @@ public void sendAccessibilityEvent(AccessibilityEvent event, int userId) { // performs the current profile parent resolution.. final int resolvedUserId = mSecurityPolicy .resolveCallingUserIdEnforcingPermissionsLocked(userId); + + // Make sure the reported package is one the caller has access to. + event.setPackageName(mSecurityPolicy.resolveValidReportedPackageLocked( + event.getPackageName(), UserHandle.getCallingAppId(), resolvedUserId)); + // This method does nothing for a background user. if (resolvedUserId == mCurrentUserId) { if (mSecurityPolicy.canDispatchAccessibilityEventLocked(event)) { @@ -627,30 +637,38 @@ public void interrupt(int userId) { @Override public int addAccessibilityInteractionConnection(IWindow windowToken, - IAccessibilityInteractionConnection connection, int userId) throws RemoteException { + IAccessibilityInteractionConnection connection, String packageName, + int userId) throws RemoteException { synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below // performs the current profile parent resolution. final int resolvedUserId = mSecurityPolicy .resolveCallingUserIdEnforcingPermissionsLocked(userId); + final int resolvedUid = UserHandle.getUid(resolvedUserId, UserHandle.getCallingAppId()); + + // Make sure the reported package is one the caller has access to. + packageName = mSecurityPolicy.resolveValidReportedPackageLocked( + packageName, UserHandle.getCallingAppId(), resolvedUserId); + final int windowId = sNextWindowId++; // If the window is from a process that runs across users such as // the system UI or the system we add it to the global state that // is shared across users. if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) { - AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper( - windowId, connection, UserHandle.USER_ALL); + RemoteAccessibilityConnection wrapper = new RemoteAccessibilityConnection( + windowId, connection, packageName, resolvedUid, UserHandle.USER_ALL); wrapper.linkToDeath(); mGlobalInteractionConnections.put(windowId, wrapper); mGlobalWindowTokens.put(windowId, windowToken.asBinder()); if (DEBUG) { Slog.i(LOG_TAG, "Added global connection for pid:" + Binder.getCallingPid() - + " with windowId: " + windowId + " and token: " + windowToken.asBinder()); + + " with windowId: " + windowId + " and token: " + + windowToken.asBinder()); } } else { - AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper( - windowId, connection, resolvedUserId); + RemoteAccessibilityConnection wrapper = new RemoteAccessibilityConnection( + windowId, connection, packageName, resolvedUid, resolvedUserId); wrapper.linkToDeath(); UserState userState = getUserStateLocked(resolvedUserId); userState.mInteractionConnections.put(windowId, wrapper); @@ -679,7 +697,8 @@ public void removeAccessibilityInteractionConnection(IWindow window) { if (removedWindowId >= 0) { if (DEBUG) { Slog.i(LOG_TAG, "Removed global connection for pid:" + Binder.getCallingPid() - + " with windowId: " + removedWindowId + " and token: " + window.asBinder()); + + " with windowId: " + removedWindowId + " and token: " + + window.asBinder()); } return; } @@ -703,13 +722,13 @@ public void removeAccessibilityInteractionConnection(IWindow window) { private int removeAccessibilityInteractionConnectionInternalLocked(IBinder windowToken, SparseArray windowTokens, - SparseArray interactionConnections) { + SparseArray interactionConnections) { final int count = windowTokens.size(); for (int i = 0; i < count; i++) { if (windowTokens.valueAt(i) == windowToken) { final int windowId = windowTokens.keyAt(i); windowTokens.removeAt(i); - AccessibilityConnectionWrapper wrapper = interactionConnections.get(windowId); + RemoteAccessibilityConnection wrapper = interactionConnections.get(windowId); wrapper.unlinkToDeath(); interactionConnections.remove(windowId); return windowId; @@ -729,9 +748,9 @@ public void setPictureInPictureActionReplacingConnection( mPictureInPictureActionReplacingConnection = null; } if (connection != null) { - AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper( + RemoteAccessibilityConnection wrapper = new RemoteAccessibilityConnection( AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID, - connection, UserHandle.USER_ALL); + connection, "foo.bar.baz", Process.SYSTEM_UID, UserHandle.USER_ALL); mPictureInPictureActionReplacingConnection = wrapper; wrapper.linkToDeath(); } @@ -2023,7 +2042,8 @@ private void updateAccessibilityShortcutLocked(UserState userState) { final long identity = Binder.clearCallingIdentity(); try { Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null, userState.mUserId); + Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null, + userState.mUserId); Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 0, userState.mUserId); @@ -2354,18 +2374,35 @@ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { } } - private class AccessibilityConnectionWrapper implements DeathRecipient { + class RemoteAccessibilityConnection implements DeathRecipient { + private final int mUid; + private final String mPackageName; private final int mWindowId; private final int mUserId; private final IAccessibilityInteractionConnection mConnection; - public AccessibilityConnectionWrapper(int windowId, - IAccessibilityInteractionConnection connection, int userId) { + RemoteAccessibilityConnection(int windowId, + IAccessibilityInteractionConnection connection, + String packageName, int uid, int userId) { mWindowId = windowId; + mPackageName = packageName; + mUid = uid; mUserId = userId; mConnection = connection; } + public int getUid() { + return mUid; + } + + public String getPackageName() { + return mPackageName; + } + + public IAccessibilityInteractionConnection getRemote() { + return mConnection; + } + public void linkToDeath() throws RemoteException { mConnection.asBinder().linkToDeath(this, 0); } @@ -3041,28 +3078,28 @@ public AccessibilityWindowInfo getWindow(int windowId) { } @Override - public boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId, + public String[] findAccessibilityNodeInfosByViewId(int accessibilityWindowId, long accessibilityNodeId, String viewIdResName, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; + RemoteAccessibilityConnection connection = null; Region partialInteractiveRegion = Region.obtain(); MagnificationSpec spec; synchronized (mLock) { mUsesAccessibilityCache = true; if (!isCalledForCurrentUserLocked()) { - return false; + return null; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { - return false; + return null; } else { connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - return false; + return null; } } if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( @@ -3075,12 +3112,14 @@ public boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId, final int interrogatingPid = Binder.getCallingPid(); callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); + final int callingUid = Binder.getCallingUid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfosByViewId(accessibilityNodeId, viewIdResName, - partialInteractiveRegion, interactionId, callback, mFetchFlags, - interrogatingPid, interrogatingTid, spec); - return true; + connection.getRemote().findAccessibilityNodeInfosByViewId(accessibilityNodeId, + viewIdResName, partialInteractiveRegion, interactionId, callback, + mFetchFlags, interrogatingPid, interrogatingTid, spec); + return mSecurityPolicy.computeValidReportedPackages(callingUid, + connection.getPackageName(), connection.getUid()); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId()."); @@ -3088,36 +3127,36 @@ public boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId, } finally { Binder.restoreCallingIdentity(identityToken); // Recycle if passed to another process. - if (partialInteractiveRegion != null && Binder.isProxy(connection)) { + if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) { partialInteractiveRegion.recycle(); } } - return false; + return null; } @Override - public boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, + public String[] findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; + RemoteAccessibilityConnection connection = null; Region partialInteractiveRegion = Region.obtain(); MagnificationSpec spec; synchronized (mLock) { mUsesAccessibilityCache = true; if (!isCalledForCurrentUserLocked()) { - return false; + return null; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { - return false; + return null; } else { connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - return false; + return null; } } if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( @@ -3130,12 +3169,14 @@ public boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, final int interrogatingPid = Binder.getCallingPid(); callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); + final int callingUid = Binder.getCallingUid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text, + connection.getRemote().findAccessibilityNodeInfosByText(accessibilityNodeId, text, partialInteractiveRegion, interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, spec); - return true; + return mSecurityPolicy.computeValidReportedPackages(callingUid, + connection.getPackageName(), connection.getUid()); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()"); @@ -3143,36 +3184,36 @@ public boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, } finally { Binder.restoreCallingIdentity(identityToken); // Recycle if passed to another process. - if (partialInteractiveRegion != null && Binder.isProxy(connection)) { + if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) { partialInteractiveRegion.recycle(); } } - return false; + return null; } @Override - public boolean findAccessibilityNodeInfoByAccessibilityId( + public String[] findAccessibilityNodeInfoByAccessibilityId( int accessibilityWindowId, long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, long interrogatingTid, Bundle arguments) throws RemoteException { final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; + RemoteAccessibilityConnection connection = null; Region partialInteractiveRegion = Region.obtain(); MagnificationSpec spec; synchronized (mLock) { mUsesAccessibilityCache = true; if (!isCalledForCurrentUserLocked()) { - return false; + return null; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { - return false; + return null; } else { connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - return false; + return null; } } if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( @@ -3185,12 +3226,14 @@ public boolean findAccessibilityNodeInfoByAccessibilityId( final int interrogatingPid = Binder.getCallingPid(); callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); + final int callingUid = Binder.getCallingUid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId, - partialInteractiveRegion, interactionId, callback, mFetchFlags | flags, - interrogatingPid, interrogatingTid, spec, arguments); - return true; + connection.getRemote().findAccessibilityNodeInfoByAccessibilityId( + accessibilityNodeId, partialInteractiveRegion, interactionId, callback, + mFetchFlags | flags, interrogatingPid, interrogatingTid, spec, arguments); + return mSecurityPolicy.computeValidReportedPackages(callingUid, + connection.getPackageName(), connection.getUid()); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()"); @@ -3198,36 +3241,36 @@ public boolean findAccessibilityNodeInfoByAccessibilityId( } finally { Binder.restoreCallingIdentity(identityToken); // Recycle if passed to another process. - if (partialInteractiveRegion != null && Binder.isProxy(connection)) { + if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) { partialInteractiveRegion.recycle(); } } - return false; + return null; } @Override - public boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, + public String[] findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; + RemoteAccessibilityConnection connection = null; Region partialInteractiveRegion = Region.obtain(); MagnificationSpec spec; synchronized (mLock) { if (!isCalledForCurrentUserLocked()) { - return false; + return null; } resolvedWindowId = resolveAccessibilityWindowIdForFindFocusLocked( accessibilityWindowId, focusType); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { - return false; + return null; } else { connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - return false; + return null; } } if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( @@ -3240,12 +3283,14 @@ public boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, final int interrogatingPid = Binder.getCallingPid(); callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); + final int callingUid = Binder.getCallingUid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findFocus(accessibilityNodeId, focusType, partialInteractiveRegion, - interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, - spec); - return true; + connection.getRemote().findFocus(accessibilityNodeId, focusType, + partialInteractiveRegion, interactionId, callback, mFetchFlags, + interrogatingPid, interrogatingTid, spec); + return mSecurityPolicy.computeValidReportedPackages(callingUid, + connection.getPackageName(), connection.getUid()); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling findFocus()"); @@ -3253,35 +3298,35 @@ public boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, } finally { Binder.restoreCallingIdentity(identityToken); // Recycle if passed to another process. - if (partialInteractiveRegion != null && Binder.isProxy(connection)) { + if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) { partialInteractiveRegion.recycle(); } } - return false; + return null; } @Override - public boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, + public String[] focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; + RemoteAccessibilityConnection connection = null; Region partialInteractiveRegion = Region.obtain(); MagnificationSpec spec; synchronized (mLock) { if (!isCalledForCurrentUserLocked()) { - return false; + return null; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { - return false; + return null; } else { connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - return false; + return null; } } if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( @@ -3294,12 +3339,14 @@ public boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, final int interrogatingPid = Binder.getCallingPid(); callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); + final int callingUid = Binder.getCallingUid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.focusSearch(accessibilityNodeId, direction, partialInteractiveRegion, - interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, - spec); - return true; + connection.getRemote().focusSearch(accessibilityNodeId, direction, + partialInteractiveRegion, interactionId, callback, mFetchFlags, + interrogatingPid, interrogatingTid, spec); + return mSecurityPolicy.computeValidReportedPackages(callingUid, + connection.getPackageName(), connection.getUid()); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling accessibilityFocusSearch()"); @@ -3307,11 +3354,11 @@ public boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, } finally { Binder.restoreCallingIdentity(identityToken); // Recycle if passed to another process. - if (partialInteractiveRegion != null && Binder.isProxy(connection)) { + if (partialInteractiveRegion != null && Binder.isProxy(connection.getRemote())) { partialInteractiveRegion.recycle(); } } - return false; + return null; } @Override @@ -3351,8 +3398,8 @@ public boolean performAccessibilityAction(int accessibilityWindowId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; IBinder activityToken = null; + RemoteAccessibilityConnection connection; synchronized (mLock) { if (!isCalledForCurrentUserLocked()) { return false; @@ -3375,7 +3422,7 @@ public boolean performAccessibilityAction(int accessibilityWindowId, if ((a11yWindowInfo != null) && a11yWindowInfo.inPictureInPicture()) { if ((mPictureInPictureActionReplacingConnection != null) && !isA11yFocusAction) { - connection = mPictureInPictureActionReplacingConnection.mConnection; + connection = mPictureInPictureActionReplacingConnection; } } } @@ -3391,8 +3438,9 @@ public boolean performAccessibilityAction(int accessibilityWindowId, LocalServices.getService(ActivityManagerInternal.class) .setFocusedActivity(activityToken); } - connection.performAccessibilityAction(accessibilityNodeId, action, arguments, - interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid); + connection.mConnection.performAccessibilityAction(accessibilityNodeId, action, + arguments, interactionId, callback, mFetchFlags, interrogatingPid, + interrogatingTid); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling performAccessibilityAction()"); @@ -4072,16 +4120,16 @@ private void toggleSplitScreen() { LocalServices.getService(StatusBarManagerInternal.class).toggleSplitScreen(); } - private IAccessibilityInteractionConnection getConnectionLocked(int windowId) { + private RemoteAccessibilityConnection getConnectionLocked(int windowId) { if (DEBUG) { Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId); } - AccessibilityConnectionWrapper wrapper = mGlobalInteractionConnections.get(windowId); + RemoteAccessibilityConnection wrapper = mGlobalInteractionConnections.get(windowId); if (wrapper == null) { wrapper = getCurrentUserStateLocked().mInteractionConnections.get(windowId); } if (wrapper != null && wrapper.mConnection != null) { - return wrapper.mConnection; + return wrapper; } if (DEBUG) { Slog.e(LOG_TAG, "No interaction connection to window: " + windowId); @@ -4230,6 +4278,16 @@ public void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available } } + private AppWidgetManagerInternal getAppWidgetManager() { + synchronized (mLock) { + if (mAppWidgetService == null + && mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) { + mAppWidgetService = LocalServices.getService(AppWidgetManagerInternal.class); + } + return mAppWidgetService; + } + } + final class WindowsForAccessibilityCallback implements WindowManagerInternal.WindowsForAccessibilityCallback { @@ -4507,6 +4565,78 @@ private boolean canDispatchAccessibilityEventLocked(AccessibilityEvent event) { } } + private boolean isValidPackageForUid(String packageName, int uid) { + try { + return uid == mPackageManager.getPackageUid( + packageName, UserHandle.getUserId(uid)); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + String resolveValidReportedPackageLocked(CharSequence packageName, int appId, int userId) { + // Okay to pass no package + if (packageName == null) { + return null; + } + // The system gets to pass any package + if (appId == Process.SYSTEM_UID) { + return packageName.toString(); + } + // Passing a package in your UID is fine + final String packageNameStr = packageName.toString(); + final int resolvedUid = UserHandle.getUid(userId, appId); + if (isValidPackageForUid(packageNameStr, resolvedUid)) { + return packageName.toString(); + } + // Appwidget hosts get to pass packages for widgets they host + final AppWidgetManagerInternal appWidgetManager = getAppWidgetManager(); + if (appWidgetManager != null && ArrayUtils.contains(appWidgetManager + .getHostedWidgetPackages(resolvedUid), packageNameStr)) { + return packageName.toString(); + } + // Otherwise, set the package to the first one in the UID + final String[] packageNames = mPackageManager.getPackagesForUid(resolvedUid); + if (ArrayUtils.isEmpty(packageNames)) { + return null; + } + // Okay, the caller reported a package it does not have access to. + // Instead of crashing the caller for better backwards compatibility + // we report the first package in the UID. Since most of the time apps + // don't use shared user id, this will yield correct results and for + // the edge case of using a shared user id we may report the wrong + // package but this is fine since first, this is a cheating app and + // second there is no way to get the correct package anyway. + return packageNames[0]; + } + + String[] computeValidReportedPackages(int callingUid, + String targetPackage, int targetUid) { + if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) { + // Empty array means any package is Okay + return EmptyArray.STRING; + } + // IMPORTANT: The target package is already vetted to be in the target UID + String[] uidPackages = new String[]{targetPackage}; + // Appwidget hosts get to pass packages for widgets they host + final AppWidgetManagerInternal appWidgetManager = getAppWidgetManager(); + if (appWidgetManager != null) { + final ArraySet widgetPackages = appWidgetManager + .getHostedWidgetPackages(targetUid); + if (widgetPackages != null && !widgetPackages.isEmpty()) { + final String[] validPackages = new String[uidPackages.length + + widgetPackages.size()]; + System.arraycopy(uidPackages, 0, validPackages, 0, uidPackages.length); + final int widgetPackageCount = widgetPackages.size(); + for (int i = 0; i < widgetPackageCount; i++) { + validPackages[uidPackages.length + i] = widgetPackages.valueAt(i); + } + return validPackages; + } + } + return uidPackages; + } + public void clearWindowsLocked() { List windows = Collections.emptyList(); final int activeWindowId = mActiveWindowId; @@ -4932,7 +5062,7 @@ private class UserState { public final RemoteCallbackList mUserClients = new RemoteCallbackList<>(); - public final SparseArray mInteractionConnections = + public final SparseArray mInteractionConnections = new SparseArray<>(); public final SparseArray mWindowTokens = new SparseArray<>(); diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 80b54770e4b72..a57010b270379 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -29,6 +29,7 @@ import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DevicePolicyManagerInternal.OnCrossProfileWidgetProvidersChangeListener; import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetManagerInternal; import android.appwidget.AppWidgetProviderInfo; import android.appwidget.PendingHostUpdate; import android.content.BroadcastReceiver; @@ -99,6 +100,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.widget.IRemoteViewsAdapterConnection; import com.android.internal.widget.IRemoteViewsFactory; @@ -107,6 +109,7 @@ import com.android.server.policy.IconUtilities; import libcore.io.IoUtils; +import libcore.util.EmptyArray; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -256,6 +259,8 @@ public void onStart() { computeMaximumWidgetBitmapMemory(); registerBroadcastReceiver(); registerOnCrossProfileProvidersChangedListener(); + + LocalServices.addService(AppWidgetManagerInternal.class, new AppWidgetManagerLocal()); } private void computeMaximumWidgetBitmapMemory() { @@ -4709,4 +4714,24 @@ public RestoreUpdateRecord(int theOldId, int theNewId) { } } } + + private class AppWidgetManagerLocal extends AppWidgetManagerInternal { + @Override + public ArraySet getHostedWidgetPackages(int uid) { + synchronized (mLock) { + ArraySet widgetPackages = null; + final int widgetCount = mWidgets.size(); + for (int i = 0; i < widgetCount; i++) { + final Widget widget = mWidgets.get(i); + if (widget.host.id.uid == uid) { + if (widgetPackages == null) { + widgetPackages = new ArraySet<>(); + } + widgetPackages.add(widget.provider.id.componentName.getPackageName()); + } + } + return widgetPackages; + } + } + } } From 4ab6bce2d969cceeafe29b59a4e77d41e292ae4e Mon Sep 17 00:00:00 2001 From: y Date: Thu, 5 Apr 2018 17:57:27 -0700 Subject: [PATCH 34/73] ResStringPool: Fix security vulnerability Adds detection of attacker-modified size and data fields passed to ResStringPool::setTo(). These attacks are modified apks that AAPT would not normally generate. In the rare case this occurs, the installation cannot be allowed to continue. Bug: 71361168 Bug: 71360999 Test: run cts -m CtsAppSecurityHostTestCases \ -t android.appsecurity.cts.CorruptApkTests Change-Id: If7eb93a9e723b16c8a0556fc4e20006aa0391d57 Merged-In: If7eb93a9e723b16c8a0556fc4e20006aa0391d57 (cherry picked from commit 7e54c3f261d81316b75cb734075319108d8bc1d1) --- libs/androidfw/ResourceTypes.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 733ffb180b3b3..401c7b070188c 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -457,6 +457,22 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) uninit(); + // The chunk must be at least the size of the string pool header. + if (size < sizeof(ResStringPool_header)) { + LOG_ALWAYS_FATAL("Bad string block: data size %zu is too small to be a string block", size); + return (mError=BAD_TYPE); + } + + // The data is at least as big as a ResChunk_header, so we can safely validate the other + // header fields. + // `data + size` is safe because the source of `size` comes from the kernel/filesystem. + if (validate_chunk(reinterpret_cast(data), sizeof(ResStringPool_header), + reinterpret_cast(data) + size, + "ResStringPool_header") != NO_ERROR) { + LOG_ALWAYS_FATAL("Bad string block: malformed block dimensions"); + return (mError=BAD_TYPE); + } + const bool notDeviceEndian = htods(0xf0) != 0xf0; if (copyData || notDeviceEndian) { @@ -468,6 +484,8 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) data = mOwnedData; } + // The size has been checked, so it is safe to read the data in the ResStringPool_header + // data structure. mHeader = (const ResStringPool_header*)data; if (notDeviceEndian) { From fcd576788c6f1457a35fc74c88f91c528e2c8d6e Mon Sep 17 00:00:00 2001 From: "Philip P. Moltmann" Date: Thu, 12 Apr 2018 14:45:14 -0700 Subject: [PATCH 35/73] DO NOT MERGE (O) Revoke permision when group changed If a run time permission of a group is already granted we grant the other permission of the group automatically when requested. Hence if an already granted permission changed its group during an update suddenly permission of a potentially not approved group will get auto-granted. This is undesirable, hence we revoke the permission during the update process. Test: atest android.permission.cts.PermissionGroupChange Change-Id: Ib2165d1ae53b80455ebe02e07775853e37a2e339 Fixes: 72710897 (cherry picked from commit 0ed1b472af194896fcf421dc862c443343c7c127) --- .../server/pm/PackageManagerService.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 06600bf75ffad..d6b572835950b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -184,6 +184,7 @@ import android.graphics.Bitmap; import android.hardware.display.DisplayManager; import android.net.Uri; +import android.os.AsyncTask; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -5849,6 +5850,83 @@ private void revokeRuntimePermission(String packageName, String name, int userId killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED); } + /** + * We might auto-grant permissions if any permission of the group is already granted. Hence if + * the group of a granted permission changes we need to revoke it to avoid having permissions of + * the new group auto-granted. + * + * @param newPackage The new package that was installed + * @param oldPackage The old package that was updated + * @param allPackageNames All package names + */ + private void revokeRuntimePermissionsIfGroupChanged( + PackageParser.Package newPackage, + PackageParser.Package oldPackage, + ArrayList allPackageNames) { + final int numOldPackagePermissions = oldPackage.permissions.size(); + final ArrayMap oldPermissionNameToGroupName + = new ArrayMap<>(numOldPackagePermissions); + + for (int i = 0; i < numOldPackagePermissions; i++) { + final PackageParser.Permission permission = oldPackage.permissions.get(i); + + if (permission.group != null) { + oldPermissionNameToGroupName.put(permission.info.name, + permission.group.info.name); + } + } + + final int numNewPackagePermissions = newPackage.permissions.size(); + for (int newPermissionNum = 0; newPermissionNum < numNewPackagePermissions; + newPermissionNum++) { + final PackageParser.Permission newPermission = + newPackage.permissions.get(newPermissionNum); + final int newProtection = newPermission.info.protectionLevel; + + if ((newProtection & PermissionInfo.PROTECTION_DANGEROUS) != 0) { + final String permissionName = newPermission.info.name; + final String newPermissionGroupName = + newPermission.group == null ? null : newPermission.group.info.name; + final String oldPermissionGroupName = oldPermissionNameToGroupName.get( + permissionName); + + if (newPermissionGroupName != null + && !newPermissionGroupName.equals(oldPermissionGroupName)) { + final List users = mContext.getSystemService(UserManager.class) + .getUsers(); + + final int numUsers = users.size(); + for (int userNum = 0; userNum < numUsers; userNum++) { + final int userId = users.get(userNum).id; + final int numPackages = allPackageNames.size(); + + for (int packageNum = 0; packageNum < numPackages; packageNum++) { + final String packageName = allPackageNames.get(packageNum); + + if (checkPermission(permissionName, packageName, userId) + == PackageManager.PERMISSION_GRANTED) { + EventLog.writeEvent(0x534e4554, "72710897", + newPackage.applicationInfo.uid, + "Revoking permission", permissionName, "from package", + packageName, "as the group changed from", + oldPermissionGroupName, "to", newPermissionGroupName); + + try { + revokeRuntimePermission(packageName, permissionName, userId, + false); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Could not revoke " + permissionName + " from " + + packageName, e); + } + } + } + } + } + } + } + } + + /** * Get the first event id for the permission. * @@ -10743,6 +10821,8 @@ private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, String primaryCpuAbiFromSettings = null; String secondaryCpuAbiFromSettings = null; + final PackageParser.Package oldPkg; + // writer synchronized (mPackages) { if (pkg.mSharedUserId != null) { @@ -10843,6 +10923,12 @@ private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, final PackageSetting disabledPkgSetting = mSettings.getDisabledSystemPkgLPr(pkg.packageName); + if (oldPkgSetting == null) { + oldPkg = null; + } else { + oldPkg = oldPkgSetting.pkg; + } + String[] usesStaticLibraries = null; if (pkg.usesStaticLibraries != null) { usesStaticLibraries = new String[pkg.usesStaticLibraries.size()]; @@ -11175,6 +11261,25 @@ private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.appId); } } + + if (oldPkg != null) { + // We need to call revokeRuntimePermissionsIfGroupChanged async as permission + // revokation from this method might need to kill apps which need the + // mPackages lock on a different thread. This would dead lock. + // + // Hence create a copy of all package names and pass it into + // revokeRuntimePermissionsIfGroupChanged. Only for those permissions might get + // revoked. If a new package is added before the async code runs the permission + // won't be granted yet, hence new packages are no problem. + final ArrayList allPackageNames = new ArrayList<>(mPackages.keySet()); + + AsyncTask.execute(new Runnable() { + public void run() { + revokeRuntimePermissionsIfGroupChanged(pkg, oldPkg, allPackageNames); + } + }); + } + return pkg; } From 2c8e19daa390addcacaeefa59df110a13ca911c6 Mon Sep 17 00:00:00 2001 From: sqian Date: Fri, 13 Apr 2018 17:10:05 -0700 Subject: [PATCH 36/73] Fix broken check for TelephonyManager#getForbiddenPlmns (backport from a fix merged in pi-dev) Bug: 73884967 Test: Treehugger Change-Id: I9deaae20893184cde36dcd936fe83708fa60b830 Merged-In: I0cf7920e138892fbcab71fae0eed1293f0b2e404 Merged-In: I9e3456e5f1e479b0e2b102f6c90db57cd0e977fe (cherry picked from commit 7b52a48d6b10e3ed2806b57d39a2d9211dd4b585) --- telephony/java/android/telephony/TelephonyManager.java | 2 +- telephony/java/com/android/internal/telephony/ITelephony.aidl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index c9afb9f9ea314..d4a8066213622 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -4605,7 +4605,7 @@ public String[] getForbiddenPlmns(int subId, int appType) { ITelephony telephony = getITelephony(); if (telephony == null) return null; - return telephony.getForbiddenPlmns(subId, appType); + return telephony.getForbiddenPlmns(subId, appType, mContext.getOpPackageName()); } catch (RemoteException ex) { return null; } catch (NullPointerException ex) { diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 9262ec5ed53bf..235b5eecb986b 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1352,12 +1352,12 @@ interface ITelephony { * Returns null if the query fails. * * - *

Requires that the calling app has READ_PRIVILEGED_PHONE_STATE + *

Requires that the calling app has READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE * * @param subId subscription ID used for authentication * @param appType the icc application type, like {@link #APPTYPE_USIM} */ - String[] getForbiddenPlmns(int subId, int appType); + String[] getForbiddenPlmns(int subId, int appType, String callingPackage); /** * Check if phone is in emergency callback mode From 9214edb61c5af7f33f7c0c861966082d9a28e21f Mon Sep 17 00:00:00 2001 From: Hansong Zhang Date: Thu, 26 Apr 2018 14:22:39 -0700 Subject: [PATCH 37/73] DO NOT MERGE Truncate newline and tab characters in BluetoothDevice name Test: manual Bug: 73173182 Change-Id: I7f2201cab36adf7f01d1a794d783cb78a536811f (cherry picked from commit 24da173b63b17a0bc6c80b2fcfefa7fe4574a15b) --- core/java/android/bluetooth/BluetoothDevice.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 98cd319a399a7..9498f125c2a6c 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -808,7 +808,11 @@ public String getName() { return null; } try { - return service.getRemoteName(this); + String name = service.getRemoteName(this); + if (name != null) { + return name.replaceAll("[\\t\\n\\r]+", " "); + } + return null; } catch (RemoteException e) { Log.e(TAG, "", e); } From 1112a8b64849ce8b366d7cc933b7080ad66be514 Mon Sep 17 00:00:00 2001 From: Ecco Park Date: Thu, 3 May 2018 14:49:40 -0700 Subject: [PATCH 38/73] Osu: fixed Mismatch between createFromParcel and writeToParcel Bug: 77600924 Change-Id: I46d765892e8e6839ed5140a3b0d6bb1815ccf9bc Signed-off-by: Ecco Park (cherry picked from commit 9a59cf84506e9fa841524ac2c70ae683449e709a) --- .../Osu/src/com/android/hotspot2/flow/OSUInfo.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/Osu/src/com/android/hotspot2/flow/OSUInfo.java b/packages/Osu/src/com/android/hotspot2/flow/OSUInfo.java index 401eccb96d050..ae47ddd22fc38 100644 --- a/packages/Osu/src/com/android/hotspot2/flow/OSUInfo.java +++ b/packages/Osu/src/com/android/hotspot2/flow/OSUInfo.java @@ -42,6 +42,7 @@ public enum IconStatus { private HSIconFileElement mIconFileElement; private String mIconFileName; private IconInfo mIconInfo; + private int mIconIndex; public OSUInfo(ScanResult scanResult, OSUProvider osuProvider, int osuID) { mOsuID = osuID; @@ -50,6 +51,7 @@ public OSUInfo(ScanResult scanResult, OSUProvider osuProvider, int osuID) { mAnqpDomID = scanResult.anqpDomainId; mAdvertisingSSID = scanResult.SSID; mOSUProvider = osuProvider; + mIconIndex = -1; } public long getOSUBssid() { @@ -157,12 +159,15 @@ public String getIconFileName() { public void setIconFileElement(HSIconFileElement iconFileElement, String fileName) { synchronized (mOSUProvider) { mIconFileElement = iconFileElement; + int index = 0; for (IconInfo iconInfo : mOSUProvider.getIcons()) { if (iconInfo.getFileName().equals(fileName)) { mIconInfo = iconInfo; mIconFileName = fileName; + mIconIndex = index; break; } + index++; } mIconStatus = IconStatus.Available; } @@ -285,9 +290,9 @@ private OSUInfo(Parcel in) { return; } mIconFileElement = new HSIconFileElement(in); - int iconIndex = in.readInt(); - mIconInfo = iconIndex >= 0 && iconIndex < mOSUProvider.getIcons().size() - ? mOSUProvider.getIcons().get(iconIndex) : null; + mIconIndex = in.readInt(); + mIconInfo = mIconIndex >= 0 && mIconIndex < mOSUProvider.getIcons().size() + ? mOSUProvider.getIcons().get(mIconIndex) : null; } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @@ -317,5 +322,6 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mIconStatus.ordinal()); mOSUProvider.writeParcel(dest); mIconFileElement.writeParcel(dest); + dest.writeInt(mIconIndex); } } From 275d49fd349461ea58d320bb66cb572e67a94e02 Mon Sep 17 00:00:00 2001 From: Eugene Susla Date: Mon, 11 Dec 2017 10:07:03 -0800 Subject: [PATCH 39/73] Nullcheck to fix Autofill CTS Test: presubmit Fixes: 70506475 Bug: 69981755 Change-Id: I187bed4889a4901a7137a2995178ea651ed09186 (cherry picked from commit 6c68a692880b7cc981e130aace0edb9f2fcffff6) --- .../AccessibilityInteractionClient.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index be3b34d0ccf8a..980a2c926c1a9 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -734,11 +734,14 @@ private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, if (info != null) { info.setConnectionId(connectionId); // Empty array means any package name is Okay - if (!ArrayUtils.isEmpty(packageNames) - && !ArrayUtils.contains(packageNames, info.getPackageName().toString())) { - // If the node package not one of the valid ones, pick the top one - this - // is one of the packages running in the introspected UID. - info.setPackageName(packageNames[0]); + if (!ArrayUtils.isEmpty(packageNames)) { + CharSequence packageName = info.getPackageName(); + if (packageName == null + || !ArrayUtils.contains(packageNames, packageName.toString())) { + // If the node package not one of the valid ones, pick the top one - this + // is one of the packages running in the introspected UID. + info.setPackageName(packageNames[0]); + } } info.setSealed(true); if (!bypassCache) { From 13343c13591f6a3054eee33c2748bf656d7e90f3 Mon Sep 17 00:00:00 2001 From: Tony Mak Date: Thu, 14 Dec 2017 12:40:07 +0000 Subject: [PATCH 40/73] clearCallingIdentity before calling into getPackageUidAsUser Fix: 70585244 Bug: 69981755 Test: Enable any accessibility service -> inflate work profile -> Tap on any work app -> no longer crash Test: cts-tradefed run cts-dev --module DevicePolicyManager --test com.android.cts.devicepolicy.CrossProfileAppsHostSideTest.testPrimaryUserToManagedProfile Change-Id: I80d18f4e2ab76a228cb0aa2c8312c323a9b5c84d (cherry picked from commit 857326e3731939f6ec7979e1d86585bf0ea484f4) --- .../server/accessibility/AccessibilityManagerService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index d0d65d94fb5b6..8b4076af7759a 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -4566,11 +4566,14 @@ private boolean canDispatchAccessibilityEventLocked(AccessibilityEvent event) { } private boolean isValidPackageForUid(String packageName, int uid) { + final long token = Binder.clearCallingIdentity(); try { return uid == mPackageManager.getPackageUid( packageName, UserHandle.getUserId(uid)); } catch (PackageManager.NameNotFoundException e) { return false; + } finally { + Binder.restoreCallingIdentity(token); } } From 007288b7a9177b63e312d8a6e3c6f33007066ad7 Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Thu, 3 May 2018 21:07:58 -0700 Subject: [PATCH 41/73] DO NOT MERGE: Fix ConnectivityController meteredness checks This patch corrects ConnectivityController's meteredness checks to perform correct meteredness checks while VPNs are running. This fixes a bug in O-MR1 where any apps using the DownloadProvider with unmetered network constraints fail to start while the VPN is enabled. This change adds a bespoke method for ConnectivityController, allowing it to correctly identify the meteredness without affecting public API surfaces. Bug: 78644887 Test: Built, flashed on Walleye, and tested. Test: Additional test coverage in subsequent patch(es). Change-Id: Ie1d11d93d51d936ce81cd5984af61bde30325983 (cherry picked from commit d08ab5a641d9d81314c9439724dd34338fa81d58) --- .../java/android/net/ConnectivityManager.java | 22 +++++++++++++++++++ .../android/net/IConnectivityManager.aidl | 1 + .../android/server/ConnectivityService.java | 12 +++++++++- .../controllers/ConnectivityController.java | 6 ++--- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index d7ecc81ffdba1..467eb9b0b0bfc 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -2504,6 +2504,28 @@ public boolean isActiveNetworkMetered() { } } + /** + * Returns if the active data network for the given UID is metered. A network + * is classified as metered when the user is sensitive to heavy data usage on + * that connection due to monetary costs, data limitations or + * battery/performance issues. You should check this before doing large + * data transfers, and warn the user or delay the operation until another + * network is available. + * + * @return {@code true} if large transfers should be avoided, otherwise + * {@code false}. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) + public boolean isActiveNetworkMeteredForUid(int uid) { + try { + return mService.isActiveNetworkMeteredForUid(uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * If the LockdownVpn mechanism is enabled, updates the vpn * with a reload of its profile. diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index a6fe7389bc723..f11372c2b31cb 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -66,6 +66,7 @@ interface IConnectivityManager NetworkQuotaInfo getActiveNetworkQuotaInfo(); boolean isActiveNetworkMetered(); + boolean isActiveNetworkMeteredForUid(int uid); boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index c1801b80af0d9..c2b83d98a8dfe 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -1339,7 +1339,17 @@ public NetworkQuotaInfo getActiveNetworkQuotaInfo() { public boolean isActiveNetworkMetered() { enforceAccessPermission(); - final int uid = Binder.getCallingUid(); + return isActiveNetworkMeteredCommon(Binder.getCallingUid()); + } + + @Override + public boolean isActiveNetworkMeteredForUid(int uid) { + enforceConnectivityInternalPermission(); + + return isActiveNetworkMeteredCommon(uid); + } + + private boolean isActiveNetworkMeteredCommon(int uid) { final NetworkCapabilities caps = getUnfilteredActiveNetworkState(uid).networkCapabilities; if (caps != null) { return !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java index 78367fe97a540..4d5a920dd54f5 100644 --- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java +++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java @@ -112,10 +112,8 @@ private boolean updateConstraintsSatisfied(JobStatus jobStatus) { final boolean connected = (info != null) && info.isConnected(); final boolean connectionUsable = connected && validated; - final boolean metered = connected && (capabilities != null) - && !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); - final boolean unmetered = connected && (capabilities != null) - && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + final boolean metered = connected && mConnManager.isActiveNetworkMeteredForUid(jobUid); + final boolean unmetered = connected && !mConnManager.isActiveNetworkMeteredForUid(jobUid); final boolean notRoaming = connected && (info != null) && !info.isRoaming(); From eef265cf5e5097e66adaf502260404fce32fca37 Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Tue, 20 Feb 2018 15:19:59 -0800 Subject: [PATCH 42/73] DO NOT MERGE: Add unit tests to ensure VPN meteredness These new tests ensure that VPNs report the meteredness of their underlying networks correctly. The added test verifies VPN meteredness for cases of metered and unmetered WiFi and Cell Bug: 78644887 Test: This; ran on walleye-eng Change-Id: I28bdc71a336bfd97f7908455d4781d774df44b87 (cherry picked from commit 66bc52884b1009fca7917ae89e72e8aa40f394d1) --- .../android/server/ConnectivityService.java | 58 ++++++++----- .../server/ConnectivityServiceTest.java | 87 +++++++++++++++++++ 2 files changed, 124 insertions(+), 21 deletions(-) diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index c2b83d98a8dfe..6e8c0d4a55c17 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -969,7 +969,7 @@ private Network[] getVpnUnderlyingNetworks(int uid) { if (!mLockdownEnabled) { int user = UserHandle.getUserId(uid); synchronized (mVpns) { - Vpn vpn = mVpns.get(user); + Vpn vpn = getVpn(user); if (vpn != null && vpn.appliesToUid(uid)) { return vpn.getUnderlyingNetworks(); } @@ -1017,7 +1017,7 @@ private boolean isNetworkWithLinkPropertiesBlocked(LinkProperties lp, int uid, return false; } synchronized (mVpns) { - final Vpn vpn = mVpns.get(UserHandle.getUserId(uid)); + final Vpn vpn = getVpn(UserHandle.getUserId(uid)); if (vpn != null && vpn.isBlockingUid(uid)) { return true; } @@ -1094,7 +1094,7 @@ private Network getActiveNetworkForUidInternal(final int uid, boolean ignoreBloc final int user = UserHandle.getUserId(uid); int vpnNetId = NETID_UNSET; synchronized (mVpns) { - final Vpn vpn = mVpns.get(user); + final Vpn vpn = getVpn(user); if (vpn != null && vpn.appliesToUid(uid)) vpnNetId = vpn.getNetId(); } NetworkAgentInfo nai; @@ -1224,7 +1224,7 @@ public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) { if (!mLockdownEnabled) { synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn != null) { Network[] networks = vpn.getUnderlyingNetworks(); if (networks != null) { @@ -3424,7 +3424,7 @@ public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPacka throwIfLockdownEnabled(); synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn != null) { return vpn.prepare(oldPackage, newPackage); } else { @@ -3451,7 +3451,7 @@ public void setVpnPackageAuthorization(String packageName, int userId, boolean a enforceCrossUserPermission(userId); synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn != null) { vpn.setPackageAuthorization(packageName, authorized); } @@ -3470,7 +3470,7 @@ public ParcelFileDescriptor establishVpn(VpnConfig config) { throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { - return mVpns.get(user).establish(config); + return getVpn(user).establish(config); } } @@ -3487,7 +3487,7 @@ public void startLegacyVpn(VpnProfile profile) { } int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { - mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress); + getVpn(user).startLegacyVpn(profile, mKeyStore, egress); } } @@ -3501,7 +3501,7 @@ public LegacyVpnInfo getLegacyVpnInfo(int userId) { enforceCrossUserPermission(userId); synchronized (mVpns) { - return mVpns.get(userId).getLegacyVpnInfo(); + return getVpn(userId).getLegacyVpnInfo(); } } @@ -3565,7 +3565,7 @@ private VpnInfo createVpnInfo(Vpn vpn) { public VpnConfig getVpnConfig(int userId) { enforceCrossUserPermission(userId); synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn != null) { return vpn.getVpnConfig(); } else { @@ -3599,7 +3599,7 @@ public boolean updateLockdownVpn() { } int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { - Vpn vpn = mVpns.get(user); + Vpn vpn = getVpn(user); if (vpn == null) { Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown"); return false; @@ -3646,7 +3646,7 @@ private void throwIfLockdownEnabled() { */ private boolean startAlwaysOnVpn(int userId) { synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn == null) { // Shouldn't happen as all codepaths that point here should have checked the Vpn // exists already. @@ -3664,7 +3664,7 @@ public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) { enforceCrossUserPermission(userId); synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn == null) { Slog.w(TAG, "User " + userId + " has no Vpn configuration"); return false; @@ -3684,7 +3684,7 @@ public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean loc } synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn == null) { Slog.w(TAG, "User " + userId + " has no Vpn configuration"); return false; @@ -3706,7 +3706,7 @@ public String getAlwaysOnVpnPackage(int userId) { enforceCrossUserPermission(userId); synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn == null) { Slog.w(TAG, "User " + userId + " has no Vpn configuration"); return null; @@ -3852,22 +3852,38 @@ public void setAirplaneMode(boolean enable) { private void onUserStart(int userId) { synchronized (mVpns) { - Vpn userVpn = mVpns.get(userId); + Vpn userVpn = getVpn(userId); if (userVpn != null) { loge("Starting user already has a VPN"); return; } userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId); - mVpns.put(userId, userVpn); + setVpn(userId, userVpn); } if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { updateLockdownVpn(); } } + /** @hide */ + @VisibleForTesting + Vpn getVpn(int userId) { + synchronized (mVpns) { + return mVpns.get(userId); + } + } + + /** @hide */ + @VisibleForTesting + void setVpn(int userId, Vpn userVpn) { + synchronized (mVpns) { + mVpns.put(userId, userVpn); + } + } + private void onUserStop(int userId) { synchronized (mVpns) { - Vpn userVpn = mVpns.get(userId); + Vpn userVpn = getVpn(userId); if (userVpn == null) { loge("Stopped user has no VPN"); return; @@ -5439,7 +5455,7 @@ public boolean addVpnAddress(String address, int prefixLength) { throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { - return mVpns.get(user).addAddress(address, prefixLength); + return getVpn(user).addAddress(address, prefixLength); } } @@ -5448,7 +5464,7 @@ public boolean removeVpnAddress(String address, int prefixLength) { throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { - return mVpns.get(user).removeAddress(address, prefixLength); + return getVpn(user).removeAddress(address, prefixLength); } } @@ -5458,7 +5474,7 @@ public boolean setUnderlyingNetworksForVpn(Network[] networks) { int user = UserHandle.getUserId(Binder.getCallingUid()); boolean success; synchronized (mVpns) { - success = mVpns.get(user).setUnderlyingNetworks(networks); + success = getVpn(user).setUnderlyingNetworks(networks); } if (success) { notifyIfacesChangedForNetworkStats(); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 6674f20317b3f..504a2dbc362b7 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -22,6 +22,7 @@ import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA; import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.ConnectivityManager.TYPE_VPN; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.NetworkCapabilities.*; @@ -102,6 +103,7 @@ import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkMonitor; import com.android.server.connectivity.NetworkMonitor.CaptivePortalProbeResult; +import com.android.server.connectivity.Vpn; import com.android.server.net.NetworkPinner; import com.android.server.net.NetworkPolicyManagerInternal; @@ -333,6 +335,9 @@ private class MockNetworkAgent { case TRANSPORT_WIFI_AWARE: mScore = 20; break; + case TRANSPORT_VPN: + mScore = 0; + break; default: throw new UnsupportedOperationException("unimplemented network type"); } @@ -868,6 +873,8 @@ private static int transportToLegacyType(int transport) { return TYPE_WIFI; case TRANSPORT_CELLULAR: return TYPE_MOBILE; + case TRANSPORT_VPN: + return TYPE_VPN; default: return TYPE_NONE; } @@ -3447,4 +3454,84 @@ private static void assertException(Runnable block, Class expected) { return; } } + + @SmallTest + public void testVpnNetworkMetered() { + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(callback); + + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + final TestNetworkCallback cellCallback = new TestNetworkCallback(); + mCm.registerNetworkCallback(cellRequest, cellCallback); + + // Setup cellular + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + verifyActiveNetwork(TRANSPORT_CELLULAR); + + // Verify meteredness of cellular + assertTrue(mCm.isActiveNetworkMetered()); + + // Setup Wifi + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + callback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + cellCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + verifyActiveNetwork(TRANSPORT_WIFI); + + // Verify meteredness of WiFi + assertTrue(mCm.isActiveNetworkMetered()); + + // Verify that setting unmetered on Wifi changes ActiveNetworkMetered + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + callback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent); + assertFalse(mCm.isActiveNetworkMetered()); + + // Setup VPN + final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); + vpnNetworkAgent.connect(true); + + Vpn mockVpn = mock(Vpn.class); + when(mockVpn.appliesToUid(anyInt())).thenReturn(true); + when(mockVpn.getNetId()).thenReturn(vpnNetworkAgent.getNetwork().netId); + + Vpn oldVpn = mService.getVpn(UserHandle.myUserId()); + mService.setVpn(UserHandle.myUserId(), mockVpn); + assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + // Verify meteredness of VPN on default network + when(mockVpn.getUnderlyingNetworks()).thenReturn(null); + assertFalse(mCm.isActiveNetworkMetered()); + assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + + // Verify meteredness of VPN on unmetered wifi + when(mockVpn.getUnderlyingNetworks()) + .thenReturn(new Network[] {mWiFiNetworkAgent.getNetwork()}); + assertFalse(mCm.isActiveNetworkMetered()); + assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + + // Set WiFi as metered, then check to see that it has been updated on the VPN + mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); + callback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent); + assertTrue(mCm.isActiveNetworkMetered()); + assertTrue(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + + // Switch to cellular + when(mockVpn.getUnderlyingNetworks()) + .thenReturn(new Network[] {mCellNetworkAgent.getNetwork()}); + assertTrue(mCm.isActiveNetworkMetered()); + assertTrue(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + + // Test unmetered cellular + mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + cellCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent); + assertFalse(mCm.isActiveNetworkMetered()); + assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + + mService.setVpn(UserHandle.myUserId(), oldVpn); + mCm.unregisterNetworkCallback(callback); + } } From 03af9f281932c163617f5dd01f1371e026d9de49 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Tue, 22 May 2018 16:56:35 +0200 Subject: [PATCH 43/73] WM: Prevent secondary display focus while keyguard is up Fixes an issue where input intended for the keyguard could end up going to a different display. To prevent this, make sure that only the default display can get focused when the keyguard is showing. Change-Id: I6463c44aedca06930d2c9bda7c45ffd93141308c Merged-In: I6463c44aedca06930d2c9bda7c45ffd93141308c Fixes: 71786287 Test: atest DisplayContentTests (cherry picked from commit 3cd5e3d9bbb3255e874b8fa27d7ed506164905dd) --- .../android/view/WindowManagerPolicy.java | 5 +++++ .../server/policy/PhoneWindowManager.java | 5 +++++ .../policy/keyguard/KeyguardStateMonitor.java | 3 +++ .../server/wm/RootWindowContainer.java | 8 ++++++++ .../server/wm/WindowManagerService.java | 13 +++++++++++++ .../server/wm/DisplayContentTests.java | 19 +++++++++++++++++++ .../server/wm/TestWindowManagerPolicy.java | 5 +++-- 7 files changed, 56 insertions(+), 2 deletions(-) diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index c4ffb4c06a26d..235e61a129f6a 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -610,6 +610,11 @@ public InputConsumer createInputConsumer(Looper looper, String name, */ void notifyKeyguardTrustedChanged(); + /** + * The keyguard showing state has changed + */ + void onKeyguardShowingAndNotOccludedChanged(); + /** * Notifies the window manager that screen is being turned off. * diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index da14c360f16ab..d3469c2346a59 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -2150,6 +2150,11 @@ public void onAppTransitionCancelledLocked(int transit) { public void onTrustedChanged() { mWindowManagerFuncs.notifyKeyguardTrustedChanged(); } + + @Override + public void onShowingChanged() { + mWindowManagerFuncs.onKeyguardShowingAndNotOccludedChanged(); + } }); } diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java index 941cd4441e23a..fd34c510d98da 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java @@ -86,6 +86,8 @@ public boolean hasLockscreenWallpaper() { @Override // Binder interface public void onShowingStateChanged(boolean showing) { mIsShowing = showing; + + mCallback.onShowingChanged(); } @Override // Binder interface @@ -119,6 +121,7 @@ public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) { public interface StateCallback { void onTrustedChanged(); + void onShowingChanged(); } public void dump(String prefix, PrintWriter pw) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index a9e56f34b0d64..5a75262a80233 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -165,10 +165,18 @@ class RootWindowContainer extends WindowContainer { } WindowState computeFocusedWindow() { + // While the keyguard is showing, we must focus anything besides the main display. + // Otherwise we risk input not going to the keyguard when the user expects it to. + final boolean forceDefaultDisplay = mService.mPolicy.isKeyguardShowingAndNotOccluded(); + for (int i = mChildren.size() - 1; i >= 0; i--) { final DisplayContent dc = mChildren.get(i); final WindowState win = dc.findFocusedWindow(); if (win != null) { + if (forceDefaultDisplay && !dc.isDefaultDisplay) { + EventLog.writeEvent(0x534e4554, "71786287", win.mOwnerUid, ""); + continue; + } return win; } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index f5cc43bd90190..d378fa3ca2294 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2928,6 +2928,11 @@ public void notifyKeyguardTrustedChanged() { mH.sendEmptyMessage(H.NOTIFY_KEYGUARD_TRUSTED_CHANGED); } + @Override + public void onKeyguardShowingAndNotOccludedChanged() { + mH.sendEmptyMessage(H.RECOMPUTE_FOCUS); + } + @Override public void screenTurningOff(ScreenOffListener listener) { mTaskSnapshotController.screenTurningOff(listener); @@ -4897,6 +4902,7 @@ final class H extends android.os.Handler { public static final int NOTIFY_KEYGUARD_FLAGS_CHANGED = 56; public static final int NOTIFY_KEYGUARD_TRUSTED_CHANGED = 57; public static final int SET_HAS_OVERLAY_UI = 58; + public static final int RECOMPUTE_FOCUS = 61; /** * Used to denote that an integer field in a message will not be used. @@ -5363,6 +5369,13 @@ public void handleMessage(Message msg) { mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1); } break; + case RECOMPUTE_FOCUS: { + synchronized (mWindowMap) { + updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, + true /* updateInputWindows */); + } + } + break; } if (DEBUG_WINDOW_TRACE) { Slog.v(TAG_WM, "handleMessage: exit"); diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java index 10d241357ff49..a6cada7b42e2c 100644 --- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java @@ -313,6 +313,25 @@ public void testFocusedWindowMultipleDisplays() throws Exception { assertEquals(window1, sWm.mRoot.computeFocusedWindow()); } + @Test + public void testKeyguard_preventsSecondaryDisplayFocus() throws Exception { + final WindowState keyguard = createWindow(null, TYPE_STATUS_BAR, + sWm.getDefaultDisplayContentLocked(), "keyguard"); + assertEquals(keyguard, sWm.mRoot.computeFocusedWindow()); + + // Add a window to a second display, and it should be focused + final DisplayContent dc = createNewDisplay(); + final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win"); + assertEquals(win, sWm.mRoot.computeFocusedWindow()); + + ((TestWindowManagerPolicy)sWm.mPolicy).keyguardShowingAndNotOccluded = true; + try { + assertEquals(keyguard, sWm.mRoot.computeFocusedWindow()); + } finally { + ((TestWindowManagerPolicy)sWm.mPolicy).keyguardShowingAndNotOccluded = false; + } + } + /** * This tests setting the maximum ui width on a display. */ diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java index eca27eefb2cd3..27ea325b8d804 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -63,6 +63,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { private static WindowManagerService sWm = null; int rotationToReport = 0; + boolean keyguardShowingAndNotOccluded = false; private Runnable mRunnableWhenAddingSplashScreen; @@ -420,7 +421,7 @@ public void exitKeyguardSecurely(OnKeyguardExitResult callback) { @Override public boolean isKeyguardLocked() { - return false; + return keyguardShowingAndNotOccluded; } @Override @@ -440,7 +441,7 @@ public boolean isKeyguardTrustedLw() { @Override public boolean isKeyguardShowingAndNotOccluded() { - return false; + return keyguardShowingAndNotOccluded; } @Override From d0c09ae726054c9d20a245dd167ade6c5a21a9f9 Mon Sep 17 00:00:00 2001 From: Todd Kennedy Date: Thu, 3 May 2018 10:05:04 +0100 Subject: [PATCH 44/73] Make safe label more safe * limit the absolute maximum size of the label to 50000 characters [which is probably far more than necessary, but, can be dialed down] * use a string buffer while processing the string [instead of creating multiple string objects] Bug: 62537081 Test: Manual. Install APK in bug and see that it can be uninstalled Change-Id: Ibf63c2691ad7438a123e92110d95b1f50050f8b1 Merged-In: Ibf63c2691ad7438a123e92110d95b1f50050f8b1 (cherry picked from commit 2263da9539daef134395226a2718ba2d7af7547d) --- .../android/content/pm/PackageItemInfo.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java index 11830c294116d..84b779466dbf9 100644 --- a/core/java/android/content/pm/PackageItemInfo.java +++ b/core/java/android/content/pm/PackageItemInfo.java @@ -42,6 +42,9 @@ */ public class PackageItemInfo { private static final float MAX_LABEL_SIZE_PX = 500f; + /** The maximum length of a safe label, in characters */ + private static final int MAX_SAFE_LABEL_LENGTH = 50000; + /** * Public name of this item. From the "android:name" attribute. */ @@ -169,7 +172,8 @@ public CharSequence loadLabel(PackageManager pm) { // If the label contains new line characters it may push the UI // down to hide a part of it. Labels shouldn't have new line // characters, so just truncate at the first time one is seen. - final int labelLength = labelStr.length(); + final int labelLength = Math.min(labelStr.length(), MAX_SAFE_LABEL_LENGTH); + final StringBuffer sb = new StringBuffer(labelLength); int offset = 0; while (offset < labelLength) { final int codePoint = labelStr.codePointAt(offset); @@ -181,14 +185,19 @@ public CharSequence loadLabel(PackageManager pm) { break; } // replace all non-break space to " " in order to be trimmed + final int charCount = Character.charCount(codePoint); if (type == Character.SPACE_SEPARATOR) { - labelStr = labelStr.substring(0, offset) + " " + labelStr.substring(offset + - Character.charCount(codePoint)); + sb.append(' '); + } else { + sb.append(labelStr.charAt(offset)); + if (charCount == 2) { + sb.append(labelStr.charAt(offset + 1)); + } } - offset += Character.charCount(codePoint); + offset += charCount; } - labelStr = labelStr.trim(); + labelStr = sb.toString().trim(); if (labelStr.isEmpty()) { return packageName; } From 92aed32dda0a6bec5cd26f4f32d8c9b103af557b Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Mon, 21 May 2018 13:59:23 -0700 Subject: [PATCH 45/73] ResStringPool: Prevenet boot loop from se fix Changes the logs adding in a previous security fix to warnings so devices with malformed APKs currently on them will not undergo DOS when they are upgraded to P. Bug: 79724567 Test: run cts -m CtsAppSecurityHostTestCases \ -t android.appsecurity.cts.CorruptApkTests Change-Id: Ied54e4bb14abdaf79da562022c7ea6075187c1f8 (cherry picked from commit f05f47b2c1838529e682ad8f931d3da72244b1a1) (cherry picked from commit c31cf80008fdb06ea8e1eab9764096653e7854b1) --- libs/androidfw/ResourceTypes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 401c7b070188c..c78554f340fc5 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -459,7 +459,7 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) // The chunk must be at least the size of the string pool header. if (size < sizeof(ResStringPool_header)) { - LOG_ALWAYS_FATAL("Bad string block: data size %zu is too small to be a string block", size); + ALOGW("Bad string block: data size %zu is too small to be a string block", size); return (mError=BAD_TYPE); } @@ -469,7 +469,7 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) if (validate_chunk(reinterpret_cast(data), sizeof(ResStringPool_header), reinterpret_cast(data) + size, "ResStringPool_header") != NO_ERROR) { - LOG_ALWAYS_FATAL("Bad string block: malformed block dimensions"); + ALOGW("Bad string block: malformed block dimensions"); return (mError=BAD_TYPE); } From 906afb4f3641963583a4334bd8291d479b8c844a Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Wed, 30 May 2018 12:17:01 -0700 Subject: [PATCH 46/73] Fix DynamicRefTable::load security bug DynamicRefTables parsed from apks are missing bounds checks that prevent buffer overflows. This changes verifies the bounds of the header before attempting to preform operations on the chunk. Bug: 79488511 Test: run cts -m CtsAppSecurityHostTestCases \ -t android.appsecurity.cts.CorruptApkTests Change-Id: I02c8ad957da244fce777ac68a482e4e8fa70f846 Merged-In: I02c8ad957da244fce777ac68a482e4e8fa70f846 (cherry picked from commit 18a6ada4aa136da4f50f03fff91d61d448ced195) --- libs/androidfw/ResourceTypes.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index c78554f340fc5..4813b4c83d041 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -6576,8 +6576,16 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, } } else if (ctype == RES_TABLE_LIBRARY_TYPE) { + if (group->dynamicRefTable.entries().size() == 0) { - status_t err = group->dynamicRefTable.load((const ResTable_lib_header*) chunk); + const ResTable_lib_header* lib = (const ResTable_lib_header*) chunk; + status_t err = validate_chunk(&lib->header, sizeof(*lib), + endPos, "ResTable_lib_header"); + if (err != NO_ERROR) { + return (mError=err); + } + + err = group->dynamicRefTable.load(lib); if (err != NO_ERROR) { return (mError=err); } From faf2dc95bf26fb075039ed3ee7623e83a281c6dd Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Tue, 12 Jun 2018 13:01:42 -0700 Subject: [PATCH 47/73] Backport Prevent shortcut info package name spoofing Test: cts-tradefed run cts -m CtsShortcutManagerTestCases -t android.content.pm.cts.shortcutmanager.ShortcutManagerFakingPublisherTest Bug: 109824443 Change-Id: I90443973aaef157d357b98b739572866125b2bbc Merged-In: I78948446a63b428ae750464194558fd44a658493 (cherry picked from commit 9e21579a11219581a0c08ff5dd6ac4dc22e988a4) --- .../android/server/pm/ShortcutService.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 15d20716a3223..83b817559c2a0 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -131,6 +131,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Predicate; @@ -1534,6 +1535,24 @@ private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) { "Ephemeral apps can't use ShortcutManager"); } + private void verifyShortcutInfoPackage(String callerPackage, ShortcutInfo si) { + if (si == null) { + return; + } + if (!Objects.equals(callerPackage, si.getPackage())) { + android.util.EventLog.writeEvent(0x534e4554, "109824443", -1, ""); + throw new SecurityException("Shortcut package name mismatch"); + } + } + + private void verifyShortcutInfoPackages( + String callerPackage, List list) { + final int size = list.size(); + for (int i = 0; i < size; i++) { + verifyShortcutInfoPackage(callerPackage, list.get(i)); + } + } + // Overridden in unit tests to execute r synchronously. void injectPostToHandler(Runnable r) { mHandler.post(r); @@ -1681,6 +1700,7 @@ public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcu verifyCaller(packageName, userId); final List newShortcuts = (List) shortcutInfoList.getList(); + verifyShortcutInfoPackages(packageName, newShortcuts); final int size = newShortcuts.size(); synchronized (mLock) { @@ -1732,6 +1752,7 @@ public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInf verifyCaller(packageName, userId); final List newShortcuts = (List) shortcutInfoList.getList(); + verifyShortcutInfoPackages(packageName, newShortcuts); final int size = newShortcuts.size(); synchronized (mLock) { @@ -1812,6 +1833,7 @@ public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcu verifyCaller(packageName, userId); final List newShortcuts = (List) shortcutInfoList.getList(); + verifyShortcutInfoPackages(packageName, newShortcuts); final int size = newShortcuts.size(); synchronized (mLock) { @@ -1871,6 +1893,7 @@ public Intent createShortcutResultIntent(String packageName, ShortcutInfo shortc Preconditions.checkNotNull(shortcut); Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); verifyCaller(packageName, userId); + verifyShortcutInfoPackage(packageName, shortcut); final Intent ret; synchronized (mLock) { @@ -1892,6 +1915,7 @@ public Intent createShortcutResultIntent(String packageName, ShortcutInfo shortc private boolean requestPinItem(String packageName, int userId, ShortcutInfo shortcut, AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent) { verifyCaller(packageName, userId); + verifyShortcutInfoPackage(packageName, shortcut); final boolean ret; synchronized (mLock) { From eabaff1c7f02906e568997bdd7dc43006655387e Mon Sep 17 00:00:00 2001 From: Arthur Ishiguro Date: Mon, 25 Jun 2018 11:31:33 -0700 Subject: [PATCH 48/73] Resolve inconsistent parcel read in NanoAppFilter Bug: 77599679 Test: Compile only Change-Id: Ib417a5cb4d51744442d2fb14437cabbe5fd1c266 (cherry picked from commit abe5a73a4a81e312a1690fbc10a6b99ce98b699a) --- core/java/android/hardware/location/NanoAppFilter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/java/android/hardware/location/NanoAppFilter.java b/core/java/android/hardware/location/NanoAppFilter.java index bf35a3d6fbd69..c44094908f3c2 100644 --- a/core/java/android/hardware/location/NanoAppFilter.java +++ b/core/java/android/hardware/location/NanoAppFilter.java @@ -83,7 +83,7 @@ private NanoAppFilter(Parcel in) { mAppId = in.readLong(); mAppVersion = in.readInt(); mVersionRestrictionMask = in.readInt(); - mAppIdVendorMask = in.readInt(); + mAppIdVendorMask = in.readLong(); } public int describeContents() { @@ -91,7 +91,6 @@ public int describeContents() { } public void writeToParcel(Parcel out, int flags) { - out.writeLong(mAppId); out.writeInt(mAppVersion); out.writeInt(mVersionRestrictionMask); From 7d8ce934a98791313f9a62d5f0196d0dec03eaf6 Mon Sep 17 00:00:00 2001 From: Bernie Innocenti Date: Mon, 28 May 2018 22:04:37 +0900 Subject: [PATCH 49/73] vpn: allow IPSec traffic through Always-on VPN This won't leak any traffic outside the VPN as long as there are no processes owned by uid 0 which generate network traffic (which is currently the case). Bug: 69873852 Test: compared the output of 'adb shell ip rule show' before and after Test: runtest -x frameworks/base/tests/net/java/com/android/server/connectivity/VpnTest.java Test: local CTS tests run: android.net.cts.VpnServiceTest Test: local CTS tests run: com.android.cts.devicepolicy.MixedDeviceOwnerTest Change-Id: I8758e576c9d961d73f62bfcf0559dd7ecee6e8e6 Merged-In: I8758e576c9d961d73f62bfcf0559dd7ecee6e8e6 Merged-In: I1f9b78c8f828ec2df7aba71b39d62be0c4db2550 Merged-In: I8edeb0942e661c8385ff0cd3fdb72e6f62a8f218 (cherry picked from commit 00000fe55a4729f8339afdc7eab5c970b2549813) (cherry picked from commit ef2910dc709d698b6476e8d462c608d04c784a26) --- .../com/android/server/connectivity/Vpn.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 56cff7c715d63..d51e1f7386597 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -94,8 +94,6 @@ import com.android.server.LocalServices; import com.android.server.net.BaseNetworkObserver; -import libcore.io.IoUtils; - import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -114,6 +112,8 @@ import java.util.TreeSet; import java.util.concurrent.atomic.AtomicInteger; +import libcore.io.IoUtils; + /** * @hide */ @@ -1184,6 +1184,18 @@ private void setVpnForcedWithExemptionsLocked(boolean enforce, /* allowedApplications */ null, /* disallowedApplications */ exemptedPackages); + // The UID range of the first user (0-99999) would block the IPSec traffic, which comes + // directly from the kernel and is marked as uid=0. So we adjust the range to allow + // it through (b/69873852). + for (UidRange range : addedRanges) { + if (range.start == 0) { + addedRanges.remove(range); + if (range.stop != 0) { + addedRanges.add(new UidRange(1, range.stop)); + } + } + } + removedRanges.removeAll(addedRanges); addedRanges.removeAll(mBlockedUsers); } From f28568c95f0624d61a1e8d48522b49a5e14b77c4 Mon Sep 17 00:00:00 2001 From: Robert Shih Date: Mon, 9 Jul 2018 13:38:31 -0700 Subject: [PATCH 50/73] Fix TrackInfo parcel write Bug: 77600398 Change-Id: Ia316f1c5dc4879f6851fdb78fe8b9039579be7bc (cherry picked from commit 0d2dc943dcaa3d7c8479e22ae62be9753ea2643c) --- media/java/android/media/MediaPlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 0d99473c69b93..f384ea56ca216 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -2363,10 +2363,10 @@ public int describeContents() { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mTrackType); + dest.writeString(mFormat.getString(MediaFormat.KEY_MIME)); dest.writeString(getLanguage()); if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { - dest.writeString(mFormat.getString(MediaFormat.KEY_MIME)); dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT)); dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_DEFAULT)); dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE)); From 89be24c647c93fb58733cdd3d06dfd5058776ae1 Mon Sep 17 00:00:00 2001 From: Mihai Popa Date: Wed, 9 May 2018 17:31:48 +0100 Subject: [PATCH 51/73] Optimise the hit test algorithm Layout#getOffsetForHorizontal was running in O(n^2) time, where n is the length of the current line. The method is used when a touch event happens on a text line, to compute the cursor offset (and the character) where it happened. Although this is not an issue in common usecases, where the number of characters on a line is relatively small, this can be very inefficient as a consequence of Unicode containing 0-width (invisible) characters. Specifically, there are characters defining the text direction (LTR or RTL), which cause our algorithm to touch the worst case quadratic runtime. For example, a person is able to send a message containing a few visible characters, and also a lot of these direction changing invisible ones. When the receiver touches the message (causing the Layout#getOffsetForHorizontal method to be called), the receiver's application would become not responsive. This CL optimizes the method to run in O(n) worst case. This is achieved by computing the measurements of all line prefixes at first, which can be done in a single pass. Then, all the prefix measurement queries will be answered in O(1), rather than O(n) as it was happening before. Bug: 79215201 Test: manual testing Change-Id: Ib66ef392c19c937718e7101f6d48fac3abe51ad0 Merged-In: Ib66ef392c19c937718e7101f6d48fac3abe51ad0 (cherry picked from commit 69b589b21d02cd380a6827c39e56cbd12a804ffd) --- core/java/android/text/Layout.java | 169 ++++++++++++++++++++++++++- core/java/android/text/TextLine.java | 92 +++++++++++++++ 2 files changed, 255 insertions(+), 6 deletions(-) diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index a233ba118e7dc..1210f43e6888a 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -975,6 +975,32 @@ public long getRunRange(int offset) { return TextUtils.packRangeInLong(0, getLineEnd(line)); } + /** + * Checks if the trailing BiDi level should be used for an offset + * + * This method is useful when the offset is at the BiDi level transition point and determine + * which run need to be used. For example, let's think about following input: (L* denotes + * Left-to-Right characters, R* denotes Right-to-Left characters.) + * Input (Logical Order): L1 L2 L3 R1 R2 R3 L4 L5 L6 + * Input (Display Order): L1 L2 L3 R3 R2 R1 L4 L5 L6 + * + * Then, think about selecting the range (3, 6). The offset=3 and offset=6 are ambiguous here + * since they are at the BiDi transition point. In Android, the offset is considered to be + * associated with the trailing run if the BiDi level of the trailing run is higher than of the + * previous run. In this case, the BiDi level of the input text is as follows: + * + * Input (Logical Order): L1 L2 L3 R1 R2 R3 L4 L5 L6 + * BiDi Run: [ Run 0 ][ Run 1 ][ Run 2 ] + * BiDi Level: 0 0 0 1 1 1 0 0 0 + * + * Thus, offset = 3 is part of Run 1 and this method returns true for offset = 3, since the BiDi + * level of Run 1 is higher than the level of Run 0. Similarly, the offset = 6 is a part of Run + * 1 and this method returns false for the offset = 6 since the BiDi level of Run 1 is higher + * than the level of Run 2. + * + * @returns true if offset is at the BiDi level transition point and trailing BiDi level is + * higher than previous BiDi level. See above for the detail. + */ private boolean primaryIsTrailingPrevious(int offset) { int line = getLineForOffset(offset); int lineStart = getLineStart(line); @@ -1024,6 +1050,41 @@ private boolean primaryIsTrailingPrevious(int offset) { return levelBefore < levelAt; } + /** + * Computes in linear time the results of calling + * #primaryIsTrailingPrevious for all offsets on a line. + * @param line The line giving the offsets we compute the information for + * @return The array of results, indexed from 0, where 0 corresponds to the line start offset + */ + private boolean[] primaryIsTrailingPreviousAllLineOffsets(int line) { + int lineStart = getLineStart(line); + int lineEnd = getLineEnd(line); + int[] runs = getLineDirections(line).mDirections; + + boolean[] trailing = new boolean[lineEnd - lineStart + 1]; + + byte[] level = new byte[lineEnd - lineStart + 1]; + for (int i = 0; i < runs.length; i += 2) { + int start = lineStart + runs[i]; + int limit = start + (runs[i + 1] & RUN_LENGTH_MASK); + if (limit > lineEnd) { + limit = lineEnd; + } + level[limit - lineStart - 1] = + (byte) ((runs[i + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK); + } + + for (int i = 0; i < runs.length; i += 2) { + int start = lineStart + runs[i]; + byte currentLevel = (byte) ((runs[i + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK); + trailing[start - lineStart] = currentLevel > (start == lineStart + ? (getParagraphDirection(line) == 1 ? 0 : 1) + : level[start - lineStart - 1]); + } + + return trailing; + } + /** * Get the primary horizontal position for the specified text offset. * This is the location where a new character would be inserted in @@ -1103,6 +1164,60 @@ private float getHorizontal(int offset, boolean trailing, int line, boolean clam return getLineStartPos(line, left, right) + wid; } + /** + * Computes in linear time the results of calling + * #getHorizontal for all offsets on a line. + * @param line The line giving the offsets we compute information for + * @param clamped Whether to clamp the results to the width of the layout + * @param primary Whether the results should be the primary or the secondary horizontal + * @return The array of results, indexed from 0, where 0 corresponds to the line start offset + */ + private float[] getLineHorizontals(int line, boolean clamped, boolean primary) { + int start = getLineStart(line); + int end = getLineEnd(line); + int dir = getParagraphDirection(line); + boolean hasTab = getLineContainsTab(line); + Directions directions = getLineDirections(line); + + TabStops tabStops = null; + if (hasTab && mText instanceof Spanned) { + // Just checking this line should be good enough, tabs should be + // consistent across all lines in a paragraph. + TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class); + if (tabs.length > 0) { + tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse + } + } + + TextLine tl = TextLine.obtain(); + tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops); + boolean[] trailings = primaryIsTrailingPreviousAllLineOffsets(line); + if (!primary) { + for (int offset = 0; offset < trailings.length; ++offset) { + trailings[offset] = !trailings[offset]; + } + } + float[] wid = tl.measureAllOffsets(trailings, null); + TextLine.recycle(tl); + + if (clamped) { + for (int offset = 0; offset <= wid.length; ++offset) { + if (wid[offset] > mWidth) { + wid[offset] = mWidth; + } + } + } + int left = getParagraphLeft(line); + int right = getParagraphRight(line); + + int lineStartPos = getLineStartPos(line, left, right); + float[] horizontal = new float[end - start + 1]; + for (int offset = 0; offset < horizontal.length; ++offset) { + horizontal[offset] = lineStartPos + wid[offset]; + } + return horizontal; + } + /** * Get the leftmost position that should be exposed for horizontal * scrolling on the specified line. @@ -1329,6 +1444,8 @@ public int getOffsetForHorizontal(int line, float horiz, boolean primary) { // XXX: we don't care about tabs as we just use TextLine#getOffsetToLeftRightOf here. tl.set(mPaint, mText, lineStartOffset, lineEndOffset, getParagraphDirection(line), dirs, false, null); + final HorizontalMeasurementProvider horizontal = + new HorizontalMeasurementProvider(line, primary); final int max; if (line == getLineCount() - 1) { @@ -1338,7 +1455,7 @@ public int getOffsetForHorizontal(int line, float horiz, boolean primary) { !isRtlCharAt(lineEndOffset - 1)) + lineStartOffset; } int best = lineStartOffset; - float bestdist = Math.abs(getHorizontal(best, primary) - horiz); + float bestdist = Math.abs(horizontal.get(lineStartOffset) - horiz); for (int i = 0; i < dirs.mDirections.length; i += 2) { int here = lineStartOffset + dirs.mDirections[i]; @@ -1354,7 +1471,7 @@ public int getOffsetForHorizontal(int line, float horiz, boolean primary) { guess = (high + low) / 2; int adguess = getOffsetAtStartOf(guess); - if (getHorizontal(adguess, primary) * swap >= horiz * swap) { + if (horizontal.get(adguess) * swap >= horiz * swap) { high = guess; } else { low = guess; @@ -1368,9 +1485,9 @@ public int getOffsetForHorizontal(int line, float horiz, boolean primary) { int aft = tl.getOffsetToLeftRightOf(low - lineStartOffset, isRtl) + lineStartOffset; low = tl.getOffsetToLeftRightOf(aft - lineStartOffset, !isRtl) + lineStartOffset; if (low >= here && low < there) { - float dist = Math.abs(getHorizontal(low, primary) - horiz); + float dist = Math.abs(horizontal.get(low) - horiz); if (aft < there) { - float other = Math.abs(getHorizontal(aft, primary) - horiz); + float other = Math.abs(horizontal.get(aft) - horiz); if (other < dist) { dist = other; @@ -1385,7 +1502,7 @@ public int getOffsetForHorizontal(int line, float horiz, boolean primary) { } } - float dist = Math.abs(getHorizontal(here, primary) - horiz); + float dist = Math.abs(horizontal.get(here) - horiz); if (dist < bestdist) { bestdist = dist; @@ -1393,7 +1510,7 @@ public int getOffsetForHorizontal(int line, float horiz, boolean primary) { } } - float dist = Math.abs(getHorizontal(max, primary) - horiz); + float dist = Math.abs(horizontal.get(max) - horiz); if (dist <= bestdist) { bestdist = dist; @@ -1404,6 +1521,46 @@ public int getOffsetForHorizontal(int line, float horiz, boolean primary) { return best; } + /** + * Responds to #getHorizontal queries, by selecting the better strategy between: + * - calling #getHorizontal explicitly for each query + * - precomputing all #getHorizontal measurements, and responding to any query in constant time + * The first strategy is used for LTR-only text, while the second is used for all other cases. + * The class is currently only used in #getOffsetForHorizontal, so reuse with care in other + * contexts. + */ + private class HorizontalMeasurementProvider { + private final int mLine; + private final boolean mPrimary; + + private float[] mHorizontals; + private int mLineStartOffset; + + HorizontalMeasurementProvider(final int line, final boolean primary) { + mLine = line; + mPrimary = primary; + init(); + } + + private void init() { + final Directions dirs = getLineDirections(mLine); + if (dirs == DIRS_ALL_LEFT_TO_RIGHT) { + return; + } + + mHorizontals = getLineHorizontals(mLine, false, mPrimary); + mLineStartOffset = getLineStart(mLine); + } + + float get(final int offset) { + if (mHorizontals == null) { + return getHorizontal(offset, mPrimary); + } else { + return mHorizontals[offset - mLineStartOffset]; + } + } + } + /** * Return the text offset after the last character on the specified line. */ diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 2dbff100375a4..55931d8f1a5fa 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -368,6 +368,98 @@ float measure(int offset, boolean trailing, FontMetricsInt fmi) { return h; } + /** + * @see #measure(int, boolean, FontMetricsInt) + * @return The measure results for all possible offsets + */ + float[] measureAllOffsets(boolean[] trailing, FontMetricsInt fmi) { + float[] measurement = new float[mLen + 1]; + + int[] target = new int[mLen + 1]; + for (int offset = 0; offset < target.length; ++offset) { + target[offset] = trailing[offset] ? offset - 1 : offset; + } + if (target[0] < 0) { + measurement[0] = 0; + } + + float h = 0; + + if (!mHasTabs) { + if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) { + for (int offset = 0; offset <= mLen; ++offset) { + measurement[offset] = measureRun(0, offset, mLen, false, fmi); + } + return measurement; + } + if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) { + for (int offset = 0; offset <= mLen; ++offset) { + measurement[offset] = measureRun(0, offset, mLen, true, fmi); + } + return measurement; + } + } + + char[] chars = mChars; + int[] runs = mDirections.mDirections; + for (int i = 0; i < runs.length; i += 2) { + int runStart = runs[i]; + int runLimit = runStart + (runs[i + 1] & Layout.RUN_LENGTH_MASK); + if (runLimit > mLen) { + runLimit = mLen; + } + boolean runIsRtl = (runs[i + 1] & Layout.RUN_RTL_FLAG) != 0; + + int segstart = runStart; + for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; ++j) { + int codept = 0; + if (mHasTabs && j < runLimit) { + codept = chars[j]; + if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) { + codept = Character.codePointAt(chars, j); + if (codept > 0xFFFF) { + ++j; + continue; + } + } + } + + if (j == runLimit || codept == '\t') { + float oldh = h; + boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl; + float w = measureRun(segstart, j, j, runIsRtl, fmi); + h += advance ? w : -w; + + float baseh = advance ? oldh : h; + FontMetricsInt crtfmi = advance ? fmi : null; + for (int offset = segstart; offset <= j && offset <= mLen; ++offset) { + if (target[offset] >= segstart && target[offset] < j) { + measurement[offset] = + baseh + measureRun(segstart, offset, j, runIsRtl, crtfmi); + } + } + + if (codept == '\t') { + if (target[j] == j) { + measurement[j] = h; + } + h = mDir * nextTab(h * mDir); + if (target[j + 1] == j) { + measurement[j + 1] = h; + } + } + + segstart = j + 1; + } + } + } + if (target[mLen] == mLen) { + measurement[mLen] = h; + } + + return measurement; + } + /** * Draws a unidirectional (but possibly multi-styled) run of text. * From ab8e82d8181a2011af40957fdd1d3b4882620899 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Tue, 7 Aug 2018 15:02:17 -0600 Subject: [PATCH 52/73] DO NOT MERGE. Persistable Uri grants still require permissions. When FLAG_GRANT_PERSISTABLE_URI_PERMISSION is requested, we still need to check permissions between the source and target packages, instead of shortcutting past them. The spirit of the original change is remains intact: if the caller requested FLAG_GRANT_PERSISTABLE_URI_PERMISSION, then we avoid returning "-1", which would prevent the grant data structure from being allocated. Bug: 111934948 Test: atest android.appsecurity.cts.AppSecurityTests Change-Id: Ief0fc922aa09fc3d9bb6a126c2ff5855347cd030 Merged-In: Ief0fc922aa09fc3d9bb6a126c2ff5855347cd030 (cherry picked from commit 05519b7e3d0f3d16ddfe6ee3892c8468a2c10c62) --- .../server/am/ActivityManagerService.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 4a2d9c9ca5a48..366e533168d0c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -8841,10 +8841,17 @@ int checkGrantUriPermissionLocked(int callingUid, String targetPkg, GrantUri gra } } - // If we're extending a persistable grant, then we always need to create - // the grant data structure so that take/release APIs work + // Figure out the value returned when access is allowed + final int allowedResult; if ((modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0) { - return targetUid; + // If we're extending a persistable grant, then we need to return + // "targetUid" so that we always create a grant data structure to + // support take/release APIs + allowedResult = targetUid; + } else { + // Otherwise, we can return "-1" to indicate that no grant data + // structures need to be created + allowedResult = -1; } if (targetUid >= 0) { @@ -8853,7 +8860,7 @@ int checkGrantUriPermissionLocked(int callingUid, String targetPkg, GrantUri gra // No need to grant the target this permission. if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "Target " + targetPkg + " already has full permission to " + grantUri); - return -1; + return allowedResult; } } else { // First... there is no target package, so can anyone access it? @@ -8869,7 +8876,7 @@ int checkGrantUriPermissionLocked(int callingUid, String targetPkg, GrantUri gra } } if (allowed) { - return -1; + return allowedResult; } } From 1a161653f388fc463bf42f5a0104f76da40baba8 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Wed, 25 Jul 2018 14:01:59 -0600 Subject: [PATCH 53/73] DO NOT MERGE. Execute "strict" queries with extra parentheses. SQLiteQueryBuilder has a setStrict() mode which can be used to detect SQL attacks from untrusted sources, which it does by running each query twice: once with an extra set of parentheses, and if that succeeds, it runs the original query verbatim. This sadly doesn't catch inputs of the type "1=1) OR (1=1", which creates valid statements for both tests above, but the final executed query ends up leaking data due to SQLite operator precedence. Instead, we need to continue compiling both variants, but we need to execute the query with the additional parentheses to ensure data won't be leaked. Test: atest cts/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java Bug: 111085900 Change-Id: I6e8746fa48f9de13adae37d2990de11c9c585381 Merged-In: I6e8746fa48f9de13adae37d2990de11c9c585381 (cherry picked from commit 5a55a72fcd18fb676eb4c114e62048068f71c01a) --- .../database/sqlite/SQLiteQueryBuilder.java | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java index 56cba795355ef..e720e21b10070 100644 --- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java +++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -376,6 +376,11 @@ public Cursor query(SQLiteDatabase db, String[] projectionIn, return null; } + final String sql; + final String unwrappedSql = buildQuery( + projectionIn, selection, groupBy, having, + sortOrder, limit); + if (mStrict && selection != null && selection.length() > 0) { // Validate the user-supplied selection to detect syntactic anomalies // in the selection string that could indicate a SQL injection attempt. @@ -384,15 +389,23 @@ public Cursor query(SQLiteDatabase db, String[] projectionIn, // originally specified. An attacker cannot create an expression that // would escape the SQL expression while maintaining balanced parentheses // in both the wrapped and original forms. - String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy, + + // NOTE: The ordering of the below operations is important; we must + // execute the wrapped query to ensure the untrusted clause has been + // fully isolated. + + // Validate the unwrapped query + db.validateSql(unwrappedSql, cancellationSignal); // will throw if query is invalid + + // Execute wrapped query for extra protection + final String wrappedSql = buildQuery(projectionIn, "(" + selection + ")", groupBy, having, sortOrder, limit); - db.validateSql(sqlForValidation, cancellationSignal); // will throw if query is invalid + sql = wrappedSql; + } else { + // Execute unwrapped query + sql = unwrappedSql; } - String sql = buildQuery( - projectionIn, selection, groupBy, having, - sortOrder, limit); - if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Performing query: " + sql); } From 136692bd4c0669611ad9a7347a18ed51f4f4e3ab Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Wed, 25 Jul 2018 14:52:14 -0600 Subject: [PATCH 54/73] DO NOT MERGE. Extend SQLiteQueryBuilder for update and delete. Developers often accept selection clauses from untrusted code, and SQLiteQueryBuilder already supports a "strict" mode to help catch SQL injection attacks. This change extends the builder to support update() and delete() calls, so that we can help secure those selection clauses too. Bug: 111085900 Test: atest packages/providers/DownloadProvider/tests/ Test: atest cts/tests/app/src/android/app/cts/DownloadManagerTest.java Test: atest cts/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java Change-Id: Ib4fc8400f184755ee7e971ab5f2095186341730c Merged-In: Ib4fc8400f184755ee7e971ab5f2095186341730c (cherry picked from commit 09d49531334ce6bc4ac45de1d3d0edb1495c0566) --- .../database/sqlite/SQLiteDatabase.java | 3 +- .../database/sqlite/SQLiteQueryBuilder.java | 245 +++++++++++++++--- 2 files changed, 218 insertions(+), 30 deletions(-) diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index df0e262b712f1..822bc2c5172dc 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -1732,7 +1732,8 @@ public void execSQL(String sql, Object[] bindArgs) throws SQLException { executeSql(sql, bindArgs); } - private int executeSql(String sql, Object[] bindArgs) throws SQLException { + /** {@hide} */ + public int executeSql(String sql, Object[] bindArgs) throws SQLException { acquireReference(); try { if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) { diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java index e720e21b10070..13ab6fb079710 100644 --- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java +++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -16,17 +16,25 @@ package android.database.sqlite; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentValues; import android.database.Cursor; import android.database.DatabaseUtils; +import android.os.Build; import android.os.CancellationSignal; import android.os.OperationCanceledException; import android.provider.BaseColumns; import android.text.TextUtils; import android.util.Log; +import libcore.util.EmptyArray; + +import java.util.Arrays; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import java.util.regex.Pattern; @@ -95,9 +103,6 @@ public void appendWhere(CharSequence inWhere) { if (mWhereClause == null) { mWhereClause = new StringBuilder(inWhere.length() + 16); } - if (mWhereClause.length() == 0) { - mWhereClause.append('('); - } mWhereClause.append(inWhere); } @@ -115,9 +120,6 @@ public void appendWhereEscapeString(String inWhere) { if (mWhereClause == null) { mWhereClause = new StringBuilder(inWhere.length() + 16); } - if (mWhereClause.length() == 0) { - mWhereClause.append('('); - } DatabaseUtils.appendEscapedSQLString(mWhereClause, inWhere); } @@ -398,7 +400,7 @@ public Cursor query(SQLiteDatabase db, String[] projectionIn, db.validateSql(unwrappedSql, cancellationSignal); // will throw if query is invalid // Execute wrapped query for extra protection - final String wrappedSql = buildQuery(projectionIn, "(" + selection + ")", groupBy, + final String wrappedSql = buildQuery(projectionIn, wrap(selection), groupBy, having, sortOrder, limit); sql = wrappedSql; } else { @@ -406,15 +408,149 @@ public Cursor query(SQLiteDatabase db, String[] projectionIn, sql = unwrappedSql; } + final String[] sqlArgs = selectionArgs; if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Performing query: " + sql); + if (Build.IS_DEBUGGABLE) { + Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs)); + } else { + Log.d(TAG, sql); + } } return db.rawQueryWithFactory( - mFactory, sql, selectionArgs, + mFactory, sql, sqlArgs, SQLiteDatabase.findEditTable(mTables), cancellationSignal); // will throw if query is invalid } + /** + * Perform an update by combining all current settings and the + * information passed into this method. + * + * @param db the database to update on + * @param selection A filter declaring which rows to return, + * formatted as an SQL WHERE clause (excluding the WHERE + * itself). Passing null will return all rows for the given URL. + * @param selectionArgs You may include ?s in selection, which + * will be replaced by the values from selectionArgs, in order + * that they appear in the selection. The values will be bound + * as Strings. + * @return the number of rows updated + * @hide + */ + public int update(@NonNull SQLiteDatabase db, @NonNull ContentValues values, + @Nullable String selection, @Nullable String[] selectionArgs) { + Objects.requireNonNull(mTables, "No tables defined"); + Objects.requireNonNull(db, "No database defined"); + Objects.requireNonNull(values, "No values defined"); + + final String sql; + final String unwrappedSql = buildUpdate(values, selection); + + if (mStrict) { + // Validate the user-supplied selection to detect syntactic anomalies + // in the selection string that could indicate a SQL injection attempt. + // The idea is to ensure that the selection clause is a valid SQL expression + // by compiling it twice: once wrapped in parentheses and once as + // originally specified. An attacker cannot create an expression that + // would escape the SQL expression while maintaining balanced parentheses + // in both the wrapped and original forms. + + // NOTE: The ordering of the below operations is important; we must + // execute the wrapped query to ensure the untrusted clause has been + // fully isolated. + + // Validate the unwrapped query + db.validateSql(unwrappedSql, null); // will throw if query is invalid + + // Execute wrapped query for extra protection + final String wrappedSql = buildUpdate(values, wrap(selection)); + sql = wrappedSql; + } else { + // Execute unwrapped query + sql = unwrappedSql; + } + + if (selectionArgs == null) { + selectionArgs = EmptyArray.STRING; + } + final String[] rawKeys = values.keySet().toArray(EmptyArray.STRING); + final int valuesLength = rawKeys.length; + final Object[] sqlArgs = new Object[valuesLength + selectionArgs.length]; + for (int i = 0; i < sqlArgs.length; i++) { + if (i < valuesLength) { + sqlArgs[i] = values.get(rawKeys[i]); + } else { + sqlArgs[i] = selectionArgs[i - valuesLength]; + } + } + if (Log.isLoggable(TAG, Log.DEBUG)) { + if (Build.IS_DEBUGGABLE) { + Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs)); + } else { + Log.d(TAG, sql); + } + } + return db.executeSql(sql, sqlArgs); + } + + /** + * Perform a delete by combining all current settings and the + * information passed into this method. + * + * @param db the database to delete on + * @param selection A filter declaring which rows to return, + * formatted as an SQL WHERE clause (excluding the WHERE + * itself). Passing null will return all rows for the given URL. + * @param selectionArgs You may include ?s in selection, which + * will be replaced by the values from selectionArgs, in order + * that they appear in the selection. The values will be bound + * as Strings. + * @return the number of rows deleted + * @hide + */ + public int delete(@NonNull SQLiteDatabase db, @Nullable String selection, + @Nullable String[] selectionArgs) { + Objects.requireNonNull(mTables, "No tables defined"); + Objects.requireNonNull(db, "No database defined"); + + final String sql; + final String unwrappedSql = buildDelete(selection); + + if (mStrict) { + // Validate the user-supplied selection to detect syntactic anomalies + // in the selection string that could indicate a SQL injection attempt. + // The idea is to ensure that the selection clause is a valid SQL expression + // by compiling it twice: once wrapped in parentheses and once as + // originally specified. An attacker cannot create an expression that + // would escape the SQL expression while maintaining balanced parentheses + // in both the wrapped and original forms. + + // NOTE: The ordering of the below operations is important; we must + // execute the wrapped query to ensure the untrusted clause has been + // fully isolated. + + // Validate the unwrapped query + db.validateSql(unwrappedSql, null); // will throw if query is invalid + + // Execute wrapped query for extra protection + final String wrappedSql = buildDelete(wrap(selection)); + sql = wrappedSql; + } else { + // Execute unwrapped query + sql = unwrappedSql; + } + + final String[] sqlArgs = selectionArgs; + if (Log.isLoggable(TAG, Log.DEBUG)) { + if (Build.IS_DEBUGGABLE) { + Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs)); + } else { + Log.d(TAG, sql); + } + } + return db.executeSql(sql, sqlArgs); + } + /** * Construct a SELECT statement suitable for use in a group of * SELECT statements that will be joined through UNION operators @@ -447,28 +583,10 @@ public String buildQuery( String[] projectionIn, String selection, String groupBy, String having, String sortOrder, String limit) { String[] projection = computeProjection(projectionIn); - - StringBuilder where = new StringBuilder(); - boolean hasBaseWhereClause = mWhereClause != null && mWhereClause.length() > 0; - - if (hasBaseWhereClause) { - where.append(mWhereClause.toString()); - where.append(')'); - } - - // Tack on the user's selection, if present. - if (selection != null && selection.length() > 0) { - if (hasBaseWhereClause) { - where.append(" AND "); - } - - where.append('('); - where.append(selection); - where.append(')'); - } + String where = computeWhere(selection); return buildQueryString( - mDistinct, mTables, projection, where.toString(), + mDistinct, mTables, projection, where, groupBy, having, sortOrder, limit); } @@ -485,6 +603,42 @@ public String buildQuery( return buildQuery(projectionIn, selection, groupBy, having, sortOrder, limit); } + /** {@hide} */ + public String buildUpdate(ContentValues values, String selection) { + if (values == null || values.size() == 0) { + throw new IllegalArgumentException("Empty values"); + } + + StringBuilder sql = new StringBuilder(120); + sql.append("UPDATE "); + sql.append(mTables); + sql.append(" SET "); + + final String[] rawKeys = values.keySet().toArray(EmptyArray.STRING); + for (int i = 0; i < rawKeys.length; i++) { + if (i > 0) { + sql.append(','); + } + sql.append(rawKeys[i]); + sql.append("=?"); + } + + final String where = computeWhere(selection); + appendClause(sql, " WHERE ", where); + return sql.toString(); + } + + /** {@hide} */ + public String buildDelete(String selection) { + StringBuilder sql = new StringBuilder(120); + sql.append("DELETE FROM "); + sql.append(mTables); + + final String where = computeWhere(selection); + appendClause(sql, " WHERE ", where); + return sql.toString(); + } + /** * Construct a SELECT statement suitable for use in a group of * SELECT statements that will be joined through UNION operators @@ -658,4 +812,37 @@ private String[] computeProjection(String[] projectionIn) { } return null; } + + private @Nullable String computeWhere(@Nullable String selection) { + final boolean hasInternal = !TextUtils.isEmpty(mWhereClause); + final boolean hasExternal = !TextUtils.isEmpty(selection); + + if (hasInternal || hasExternal) { + final StringBuilder where = new StringBuilder(); + if (hasInternal) { + where.append('(').append(mWhereClause).append(')'); + } + if (hasInternal && hasExternal) { + where.append(" AND "); + } + if (hasExternal) { + where.append('(').append(selection).append(')'); + } + return where.toString(); + } else { + return null; + } + } + + /** + * Wrap given argument in parenthesis, unless it's {@code null} or + * {@code ()}, in which case return it verbatim. + */ + private @Nullable String wrap(@Nullable String arg) { + if (TextUtils.isEmpty(arg)) { + return arg; + } else { + return "(" + arg + ")"; + } + } } From 1cd4656371f50289432ecf8f6aa5179d3b8b2442 Mon Sep 17 00:00:00 2001 From: akirilov Date: Wed, 18 Jul 2018 17:50:05 -0700 Subject: [PATCH 55/73] RESTRICT AUTOMERGE: Revoke permissions defined in a to-be removed package. Bug: 67319274 Test: run cts-dev --module CtsPermissionTestCases --test android.permission.cts.RemovePermissionTest#permissionShouldBeRevokedIfRemoved Change-Id: I2771c048e13529e168121c5a5501aa26fc21e30f (cherry picked from commit 821076440434443af4257c5ba0b7b1f037735041) --- .../server/pm/PackageManagerService.java | 58 +++++++++++++++++-- .../java/com/android/server/pm/Settings.java | 11 ++++ 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index d6b572835950b..287bce50cc465 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -624,6 +624,8 @@ public class PackageManagerService extends IPackageManager.Stub */ private static final boolean DEFAULT_PACKAGE_PARSER_CACHE_ENABLED = true; + private static final int PROTECTION_MASK_BASE = 0xf; + final ServiceThread mHandlerThread; final PackageHandler mHandler; @@ -5761,11 +5763,12 @@ public void run() { @Override public void revokeRuntimePermission(String packageName, String name, int userId) { - revokeRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */); + revokeRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */, + mSettings.getPermission(name)); } private void revokeRuntimePermission(String packageName, String name, int userId, - boolean overridePolicy) { + boolean overridePolicy, BasePermission bp) { if (!sUserManager.exists(userId)) { Log.e(TAG, "No such user:" + userId); return; @@ -5791,7 +5794,6 @@ private void revokeRuntimePermission(String packageName, String name, int userId || filterAppAccessLPr(ps, Binder.getCallingUid(), userId)) { throw new IllegalArgumentException("Unknown package: " + packageName); } - final BasePermission bp = mSettings.mPermissions.get(name); if (bp == null) { throw new IllegalArgumentException("Unknown permission: " + name); } @@ -5913,7 +5915,7 @@ private void revokeRuntimePermissionsIfGroupChanged( try { revokeRuntimePermission(packageName, permissionName, userId, - false); + false, mSettings.getPermission(permissionName)); } catch (IllegalArgumentException e) { Slog.e(TAG, "Could not revoke " + permissionName + " from " + packageName, e); @@ -12792,7 +12794,10 @@ void cleanPackageDataStructuresLILPw(PackageParser.Package pkg, boolean chatty) if (DEBUG_REMOVE) Log.d(TAG, " Activities: " + r); } + final ArrayList allPackageNames = new ArrayList<>(mPackages.keySet()); + N = pkg.permissions.size(); + List bps = new ArrayList(N); r = null; for (i=0; i { + final int numRemovedPermissions = bps.size(); + for (int permissionNum = 0; permissionNum < numRemovedPermissions; permissionNum++) { + final int[] userIds = mUserManagerInternal.getUserIds(); + final int numUserIds = userIds.length; + + final int numPackages = allPackageNames.size(); + for (int packageNum = 0; packageNum < numPackages; packageNum++) { + final String packageName = allPackageNames.get(packageNum); + final PackageManagerInternal packageManagerInt = + LocalServices.getService(PackageManagerInternal.class); + final ApplicationInfo applicationInfo = packageManagerInt.getApplicationInfo( + packageName, 0, Process.SYSTEM_UID, UserHandle.USER_SYSTEM); + if (applicationInfo != null + && applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) { + continue; + } + for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) { + final int userId = userIds[userIdNum]; + final String permissionName = bps.get(permissionNum).name; + if (checkPermission(permissionName, packageName, + userId) == PackageManager.PERMISSION_GRANTED) { + try { + revokeRuntimePermission(packageName, + permissionName, + userId, + false, + bps.get(permissionNum)); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Could not revoke " + permissionName + " from " + + packageName, e); + } + } + } + } + } + }); + if (r != null) { if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); } @@ -25525,7 +25573,7 @@ public void grantRuntimePermission(String packageName, String name, int userId, public void revokeRuntimePermission(String packageName, String name, int userId, boolean overridePolicy) { PackageManagerService.this.revokeRuntimePermission(packageName, name, userId, - overridePolicy); + overridePolicy, mSettings.getPermission(name)); } @Override diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 56835f69a3c78..c116d833213ed 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -449,6 +449,17 @@ public void forceCurrent() { mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml"); } + public @Nullable BasePermission getPermission(@NonNull String permName) { + synchronized (mLock) { + return getPermissionLocked(permName); + } + } + + @GuardedBy("mLock") + @Nullable BasePermission getPermissionLocked(@NonNull String permName) { + return mPermissions.get(permName); + } + PackageSetting getPackageLPr(String pkgName) { return mPackages.get(pkgName); } From 6033d76bc68c04155dfa763ac86a229169d78cfb Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Thu, 19 Jul 2018 16:22:02 -0700 Subject: [PATCH 56/73] Fix crash during cursor moving on BiDi text The crash was introduced by Ib66ef392c19c937718e7101f6d48fac3abe51ad0 The root cause of the crashing is requesting out-of-line access for the horizontal width. This invalid access is silently ignored by TextLine#measure() method but new implementation end up with out of bounds access. To makes behavior as old implementation, calling getHorizontal instead of accessing measured result array. Bug: 78464361, 111580019 Test: Manually done Change-Id: I5c5778718f6b397adbb1e4f2cf95e9f635f6e5c8 (cherry picked from commit 960647d582911ae7ab8b9491097898e6c313aaf1) Merged-In: I5c5778718f6b397adbb1e4f2cf95e9f635f6e5c8 (cherry picked from commit d30c55e3ccf25668fcbccf29c94224fd1d2e67b6) --- core/java/android/text/Layout.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 1210f43e6888a..84ef9435f64ce 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1553,7 +1553,8 @@ private void init() { } float get(final int offset) { - if (mHorizontals == null) { + if (mHorizontals == null || offset < mLineStartOffset + || offset >= mLineStartOffset + mHorizontals.length) { return getHorizontal(offset, mPrimary); } else { return mHorizontals[offset - mLineStartOffset]; From c072f6640a1fc671b4147913c955c535b7e8a465 Mon Sep 17 00:00:00 2001 From: IacobIonut01 Date: Thu, 28 Jun 2018 13:01:02 +0300 Subject: [PATCH 57/73] Implement Android P Volume Panel Change-Id: I32ff6166d4fdae104381cf644a60c791687d108f Signed-off-by: IacobIonut01 (cherry picked from commit 2c1d71998320bd257ff89baccae174bbb491d032) --- .../res/drawable/ic_volume_collapse.xml | 11 +- .../res/drawable/ic_volume_expand.xml | 2 +- .../SystemUI/res/layout/volume_dialog.xml | 102 ++++++++++-------- .../SystemUI/res/layout/volume_dialog_row.xml | 64 +++++------ .../SystemUI/res/layout/volume_zen_footer.xml | 6 +- .../SystemUI/res/layout/zen_mode_panel.xml | 4 +- .../systemui/volume/VolumeDialogImpl.java | 28 ++--- .../systemui/volume/VolumeDialogMotion.java | 9 +- 8 files changed, 112 insertions(+), 114 deletions(-) diff --git a/packages/SystemUI/res/drawable/ic_volume_collapse.xml b/packages/SystemUI/res/drawable/ic_volume_collapse.xml index 3853d121ce2ca..3e045362d2937 100644 --- a/packages/SystemUI/res/drawable/ic_volume_collapse.xml +++ b/packages/SystemUI/res/drawable/ic_volume_collapse.xml @@ -26,8 +26,8 @@ + android:translateY="13.587" + android:rotation="135" > @@ -40,11 +40,12 @@ + android:translateY="13.587" + android:rotation="-135" > + android:translateX="4" + android:translateY="-2"> + android:translateX="2" > - + android:layout_height="match_parent" + android:layout_marginBottom="0dp" + android:clipChildren="false" + android:clipToPadding="false" + android:paddingTop="@dimen/global_actions_top_padding" + android:theme="@style/qs_theme"> + android:layout_gravity="top|right" + android:orientation="vertical"> - - + android:orientation="vertical"> - + + + - - - + - - + + + + + - + android:orientation="vertical"> + + + + - + + diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml index 699caae997737..9853d5f3aee16 100644 --- a/packages/SystemUI/res/layout/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout/volume_dialog_row.xml @@ -13,45 +13,45 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - + android:orientation="vertical"> + android:layout_width="@dimen/global_actions_panel_width" + android:layout_height="204dp" + android:layout_marginTop="8dp" + android:orientation="vertical"> + + android:id="@+id/volume_row_icon" + style="@style/VolumeButtons" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:gravity="center" + android:soundEffectsEnabled="false" /> + android:id="@+id/volume_row_slider" + android:layout_width="148dp" + android:layout_height="match_parent" + android:layout_gravity="center" + android:gravity="center" + android:rotation="-90" /> - + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_zen_footer.xml b/packages/SystemUI/res/layout/volume_zen_footer.xml index df79c5ff5ba47..ab3c823efa640 100644 --- a/packages/SystemUI/res/layout/volume_zen_footer.xml +++ b/packages/SystemUI/res/layout/volume_zen_footer.xml @@ -16,10 +16,10 @@ + android:visibility="gone"> Date: Tue, 12 Jun 2018 01:00:55 +0200 Subject: [PATCH 58/73] Stock recents: use round corners for headers Change-Id: I54983ed02d30703a3f4ae7f280c98cd2bc61d838 --- packages/SystemUI/res/values/dimens.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a186639638ffd..356b4c644baa6 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -758,8 +758,8 @@ - 2dp - 6dp + 6dp + 10dp 1dp From 231d7a9c5c7457d0362e1a426a99ac21a21d0749 Mon Sep 17 00:00:00 2001 From: aoleary Date: Fri, 24 Aug 2018 16:19:44 +0200 Subject: [PATCH 59/73] Add Smart pixels tile to QS tiles options --- packages/SystemUI/res/values/config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 6a038f4ff19e0..70912017d171f 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -112,7 +112,7 @@ - wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,nfc,location,hotspot,inversion,saver,work,cast,night,screenshot,caffeine,reboot,pip,sound,heads_up,screenrecord,volume,hwkeys,navigation,weather,theme + wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,nfc,location,hotspot,inversion,saver,work,cast,night,screenshot,caffeine,reboot,pip,sound,heads_up,screenrecord,volume,hwkeys,navigation,weather,smartpixels,theme From 2163527e5db06bb3f101a79122af1ee5e90d99fa Mon Sep 17 00:00:00 2001 From: Elisa Pascual Trevino Date: Wed, 29 Aug 2018 23:20:38 +0000 Subject: [PATCH 60/73] Revert "RESTRICT AUTOMERGE: Revoke permissions defined in a to-be removed package." This reverts commit 821076440434443af4257c5ba0b7b1f037735041. Reason for revert: b/111752150 Change-Id: I035cfcacaeaf798b8aea7fe62376624d06c64388 (cherry picked from commit 9cd13a2bd5ca2546da7a15182b0ddf1a81f2e7da) --- .../server/pm/PackageManagerService.java | 58 ++----------------- .../java/com/android/server/pm/Settings.java | 11 ---- 2 files changed, 5 insertions(+), 64 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 287bce50cc465..d6b572835950b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -624,8 +624,6 @@ public class PackageManagerService extends IPackageManager.Stub */ private static final boolean DEFAULT_PACKAGE_PARSER_CACHE_ENABLED = true; - private static final int PROTECTION_MASK_BASE = 0xf; - final ServiceThread mHandlerThread; final PackageHandler mHandler; @@ -5763,12 +5761,11 @@ public void run() { @Override public void revokeRuntimePermission(String packageName, String name, int userId) { - revokeRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */, - mSettings.getPermission(name)); + revokeRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */); } private void revokeRuntimePermission(String packageName, String name, int userId, - boolean overridePolicy, BasePermission bp) { + boolean overridePolicy) { if (!sUserManager.exists(userId)) { Log.e(TAG, "No such user:" + userId); return; @@ -5794,6 +5791,7 @@ private void revokeRuntimePermission(String packageName, String name, int userId || filterAppAccessLPr(ps, Binder.getCallingUid(), userId)) { throw new IllegalArgumentException("Unknown package: " + packageName); } + final BasePermission bp = mSettings.mPermissions.get(name); if (bp == null) { throw new IllegalArgumentException("Unknown permission: " + name); } @@ -5915,7 +5913,7 @@ private void revokeRuntimePermissionsIfGroupChanged( try { revokeRuntimePermission(packageName, permissionName, userId, - false, mSettings.getPermission(permissionName)); + false); } catch (IllegalArgumentException e) { Slog.e(TAG, "Could not revoke " + permissionName + " from " + packageName, e); @@ -12794,10 +12792,7 @@ void cleanPackageDataStructuresLILPw(PackageParser.Package pkg, boolean chatty) if (DEBUG_REMOVE) Log.d(TAG, " Activities: " + r); } - final ArrayList allPackageNames = new ArrayList<>(mPackages.keySet()); - N = pkg.permissions.size(); - List bps = new ArrayList(N); r = null; for (i=0; i { - final int numRemovedPermissions = bps.size(); - for (int permissionNum = 0; permissionNum < numRemovedPermissions; permissionNum++) { - final int[] userIds = mUserManagerInternal.getUserIds(); - final int numUserIds = userIds.length; - - final int numPackages = allPackageNames.size(); - for (int packageNum = 0; packageNum < numPackages; packageNum++) { - final String packageName = allPackageNames.get(packageNum); - final PackageManagerInternal packageManagerInt = - LocalServices.getService(PackageManagerInternal.class); - final ApplicationInfo applicationInfo = packageManagerInt.getApplicationInfo( - packageName, 0, Process.SYSTEM_UID, UserHandle.USER_SYSTEM); - if (applicationInfo != null - && applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) { - continue; - } - for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) { - final int userId = userIds[userIdNum]; - final String permissionName = bps.get(permissionNum).name; - if (checkPermission(permissionName, packageName, - userId) == PackageManager.PERMISSION_GRANTED) { - try { - revokeRuntimePermission(packageName, - permissionName, - userId, - false, - bps.get(permissionNum)); - } catch (IllegalArgumentException e) { - Slog.e(TAG, "Could not revoke " + permissionName + " from " - + packageName, e); - } - } - } - } - } - }); - if (r != null) { if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); } @@ -25573,7 +25525,7 @@ public void grantRuntimePermission(String packageName, String name, int userId, public void revokeRuntimePermission(String packageName, String name, int userId, boolean overridePolicy) { PackageManagerService.this.revokeRuntimePermission(packageName, name, userId, - overridePolicy, mSettings.getPermission(name)); + overridePolicy); } @Override diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index c116d833213ed..56835f69a3c78 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -449,17 +449,6 @@ public void forceCurrent() { mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml"); } - public @Nullable BasePermission getPermission(@NonNull String permName) { - synchronized (mLock) { - return getPermissionLocked(permName); - } - } - - @GuardedBy("mLock") - @Nullable BasePermission getPermissionLocked(@NonNull String permName) { - return mPermissions.get(permName); - } - PackageSetting getPackageLPr(String pkgName) { return mPackages.get(pkgName); } From c9bc35a45da1e765eb36af604c0c580bd66644cc Mon Sep 17 00:00:00 2001 From: Wale Ogunwale Date: Wed, 16 May 2018 16:42:29 -0700 Subject: [PATCH 61/73] RESTRICT AUTOMERGE: Hide overlay windows when requesting media projection permission. 1: Cherry-pick ag/4067454 - Setting PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS updateNonSystemOverlayWindowsVisibilityIfNeeded on relayoutWindow 2: Cherry-pick ag/3650369 - If PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS changed on relayoutWindow() then updateNonSystemOverlayWindowsVisibilityIfNeeded 3: Add permissions to SystemUI to allow it to hide non-system overlays Bug: 34170870 Test: manual (see bug for poc) Change-Id: I57cb0f390d9a78e721c5ddce49a377d385002753 (cherry picked from commit 40f7b5832291ec81b921d9d7598447653d926604) --- packages/SystemUI/AndroidManifest.xml | 3 +++ .../media/MediaProjectionPermissionActivity.java | 7 ++++++- .../java/com/android/server/wm/WindowManagerService.java | 9 ++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 070634b83552c..2f7f0bb662ac4 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -199,6 +199,9 @@ + + + names) { return false; } + /** + * Returns whether the state has any known request for the given permission name, + * whether or not it has been granted. + */ + public boolean hasRequestedPermission(String name) { + return mPermissions != null && (mPermissions.get(name) != null); + } + /** * Gets all permissions for a given device user id regardless if they * are install time or runtime permissions. From 117ca495395973b30ccbffd2a2eb0e26e205f9c8 Mon Sep 17 00:00:00 2001 From: Michael Wachenschwanz Date: Fri, 24 Aug 2018 21:50:35 -0700 Subject: [PATCH 63/73] Verify number of Map entries written to Parcel Make sure the number of entries written by Parcel#writeMapInternal matches the size written. If a mismatch were allowed, an exploitable scenario could occur where the data read from the Parcel would not match the data written. Fixes: 112859604 Test: cts-tradefed run cts -m CtsOsTestCases -t android.os.cts.ParcelTest Change-Id: I325d08a8b66b6e80fe76501359c41b6656848607 Merged-In: I325d08a8b66b6e80fe76501359c41b6656848607 (cherry picked from commit 057a01d1f38e9b46d3faa4059fdd7c8717681ea0) --- core/java/android/os/Parcel.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index c6d3860c66006..7da3e1fded30b 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -806,11 +806,19 @@ public final void writeMap(Map val) { return; } Set> entries = val.entrySet(); - writeInt(entries.size()); + int size = entries.size(); + writeInt(size); + for (Map.Entry e : entries) { writeValue(e.getKey()); writeValue(e.getValue()); + size--; } + + if (size != 0) { + throw new BadParcelableException("Map size does not match number of entries!"); + } + } /** From 2671bd0039c44c81d19b7781a6f6b2e447570c96 Mon Sep 17 00:00:00 2001 From: Jmz Date: Mon, 6 Jan 2014 15:30:15 -0500 Subject: [PATCH 64/73] Force Expanded Notifications [1/2] * This will force expanded notifications for all apps that support expanded notifications Change-Id: I2259ae02bbdeb0867b83a52277b5f7ff2de6485d Signed-off-by: Corey Edwards (cherry picked from commit 776a7bb42387c01ef9b30f8bbf69da38712b7b21) --- core/java/android/provider/Settings.java | 6 ++++++ .../systemui/statusbar/ExpandableNotificationRow.java | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c1a67377efa82..e250c5e206866 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3797,6 +3797,12 @@ public boolean validate(String value) { /** @hide */ public static final Validator WINDOW_ORIENTATION_LISTENER_LOG_VALIDATOR = sBooleanValidator; + /** + * Force expanded notifications on all apps that support it. + * @hide + */ + public static final String FORCE_EXPANDED_NOTIFICATIONS = "force_expanded_notifications"; + /** * @deprecated Use {@link android.provider.Settings.Global#POWER_SOUNDS_ENABLED} * instead diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 977f0d48311e6..c383072e41832 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -32,6 +32,7 @@ import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; +import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.FloatProperty; @@ -1529,7 +1530,12 @@ public boolean hasUserChangedExpansion() { } public boolean isUserExpanded() { - return mUserExpanded; + if (Settings.System.getInt(mContext.getContentResolver(), + Settings.System.FORCE_EXPANDED_NOTIFICATIONS, 0) != 1) { + return mUserExpanded; + } else { + return true; + } } /** From 6cf16e78a48f11a136f1c163b2f040d4f7ebbf69 Mon Sep 17 00:00:00 2001 From: Arasthel Date: Fri, 23 Feb 2018 13:21:49 +0100 Subject: [PATCH 65/73] Add edge gestures (1/2) From: Kyrylo Mikos Date: Thu, 14 Nov 2013 23:07:12 +0200 Subject: [PATCH] Add EdgeGesture service From: Kyrylo Mikos Date: Fri, 15 Nov 2013 18:21:50 +0000 Subject: Add ability to use EdgeGestureService for system gestures From: Valters Strods Date: Thu, 26 Nov 2015 01:07:57 +0200 Subject: Add NPE handling to the Edge Gesture Manager It seems that the Edge Gesture Manager sometimes gets called when the service has not popped up. This commit just gracefully handles these issues by essentially ignoring them, instead of throwing massively in the logs. Change-Id: Ie510411778ce400ef04f9fcd84dd27091807312b --- Android.mk | 3 + core/java/android/provider/Settings.java | 43 ++ .../service/gesture/EdgeGestureManager.java | 213 +++++++ .../IEdgeGestureActivationListener.aidl | 14 + .../gesture/IEdgeGestureHostCallback.aidl | 20 + .../service/gesture/IEdgeGestureService.aidl | 20 + .../java/android/service/gesture/package.html | 5 + core/java/android/view/InputDevice.java | 9 + .../util/gesture/EdgeGesturePosition.java | 42 ++ .../util/gesture/EdgeServiceConstants.java | 82 +++ core/res/res/values/aos_config.xml | 5 + core/res/res/values/aos_dimens.xml | 12 + .../systemui/statusbar/phone/StatusBar.java | 32 ++ .../ScreenGesturesController.java | 176 ++++++ .../screen_gestures/ScreenGesturesView.java | 222 ++++++++ .../server/policy/PhoneWindowManager.java | 90 +++ .../java/com/android/server/SystemServer.java | 16 +- .../gesture/EdgeGestureInputFilter.java | 539 ++++++++++++++++++ .../server/gesture/EdgeGestureService.java | 486 ++++++++++++++++ .../server/gesture/EdgeGestureTracker.java | 252 ++++++++ 20 files changed, 2278 insertions(+), 3 deletions(-) create mode 100644 core/java/android/service/gesture/EdgeGestureManager.java create mode 100644 core/java/android/service/gesture/IEdgeGestureActivationListener.aidl create mode 100644 core/java/android/service/gesture/IEdgeGestureHostCallback.aidl create mode 100644 core/java/android/service/gesture/IEdgeGestureService.aidl create mode 100644 core/java/android/service/gesture/package.html create mode 100644 core/java/com/android/internal/util/gesture/EdgeGesturePosition.java create mode 100644 core/java/com/android/internal/util/gesture/EdgeServiceConstants.java create mode 100644 core/res/res/values/aos_dimens.xml create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesController.java create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesView.java create mode 100644 services/java/com/android/server/gesture/EdgeGestureInputFilter.java create mode 100644 services/java/com/android/server/gesture/EdgeGestureService.java create mode 100644 services/java/com/android/server/gesture/EdgeGestureTracker.java diff --git a/Android.mk b/Android.mk index cf82b96f6419e..2a26498d30cb0 100644 --- a/Android.mk +++ b/Android.mk @@ -342,6 +342,9 @@ LOCAL_SRC_FILES += \ core/java/android/service/voice/IVoiceInteractionSession.aidl \ core/java/android/service/voice/IVoiceInteractionSessionService.aidl \ core/java/android/service/gesture/IGestureService.aidl \ + core/java/android/service/gesture/IEdgeGestureService.aidl \ + core/java/android/service/gesture/IEdgeGestureActivationListener.aidl \ + core/java/android/service/gesture/IEdgeGestureHostCallback.aidl \ core/java/android/service/wallpaper/IWallpaperConnection.aidl \ core/java/android/service/wallpaper/IWallpaperEngine.aidl \ core/java/android/service/wallpaper/IWallpaperService.aidl \ diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e250c5e206866..ad72e9b961668 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4834,6 +4834,13 @@ public boolean validate(String value) { */ public static final String CHOOSER_ACTIVITY_BLACKLIST = "chooser_activity_blacklist"; + /** + * Use EdgeGesture Service for system gestures in PhoneWindowManager + * @hide + */ + public static final String USE_EDGE_SERVICE_FOR_GESTURES = "edge_service_for_gestures"; + + /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. @@ -8456,6 +8463,42 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val */ public static final String STATUS_BAR_BATTERY_STYLE = "status_bar_battery_style"; + /** + * Whether to use edge gestures to navigate. + * @hide + */ + public static final String EDGE_GESTURES_ENABLED = "edge_gestures_enabled"; + + /** + * Haptic feedback duration on edge gesture navigation. + * @hide + */ + public static final String EDGE_GESTURES_FEEDBACK_DURATION = "edge_gestures_feedback_duration"; + + /** + * Long press duration on edge gesture navigation. + * @hide + */ + public static final String EDGE_GESTURES_LONG_PRESS_DURATION = "edge_gestures_long_press_duration"; + + /** + * Back gesture active on this edges. + * @hide + */ + public static final String EDGE_GESTURES_BACK_EDGES = "edge_gestures_back_edges"; + + /** + * Back gesture active on this edges when on landscape. + * @hide + */ + public static final String EDGE_GESTURES_LANDSCAPE_BACK_EDGES = "edge_gestures_landscape_back_edges"; + + /** + * Activate back gestures only when Y position > than this % of screen. + * @hide + */ + public static final String EDGE_GESTURES_BACK_SCREEN_PERCENT = "edge_gestures_back_screen_percent"; + /** * This are the settings to be backed up. * diff --git a/core/java/android/service/gesture/EdgeGestureManager.java b/core/java/android/service/gesture/EdgeGestureManager.java new file mode 100644 index 0000000000000..18818443f9374 --- /dev/null +++ b/core/java/android/service/gesture/EdgeGestureManager.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project (Jens Doll) + * + * 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.service.gesture; + +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.service.gesture.IEdgeGestureActivationListener; +import android.service.gesture.IEdgeGestureHostCallback; +import android.service.gesture.IEdgeGestureService; +import android.util.Slog; + +import com.android.internal.util.gesture.EdgeGesturePosition; + +/** + * This is a simple Manager class for edge gesture service on the application side. The application need + * {@code INJECT_EVENTS} permission to register {@code EdgeGestureActivationListener}s.
+ * See {@link android.service.gesture.IEdgeGestureService} for more information. + * + * @see android.service.gesture.IEdgeGestureService + * @hide + */ +public class EdgeGestureManager { + public static final String TAG = "EdgeGestureManager"; + public static final boolean DEBUG = false; + + private static EdgeGestureManager sInstance; + + private final IEdgeGestureService mPs; + + public static abstract class EdgeGestureActivationListener { + private Handler mHandler; + private IEdgeGestureHostCallback mCallback; + + private class Delegator extends IEdgeGestureActivationListener.Stub { + public void onEdgeGestureActivation(final int touchX, final int touchY, final int positionIndex, final int flags) + throws RemoteException { + mHandler.post(new Runnable() { + public void run() { + EdgeGestureActivationListener.this.onEdgeGestureActivation(touchX, touchY, EdgeGesturePosition.values()[positionIndex], flags); + } + }); + } + } + private Delegator mDelegator; + + public EdgeGestureActivationListener() { + this(Looper.getMainLooper()); + } + + public EdgeGestureActivationListener(Looper looper) { + mHandler = new Handler(looper); + mDelegator = new Delegator(); + } + + /* package */ void setHostCallback(IEdgeGestureHostCallback hostCallback) { + mCallback = hostCallback; + } + + /** + * Override this to receive activations from the edge gesture service. + * + * @param touchX the last X position a touch event was registered. + * @param touchY the last Y position a touch event was registered. + * @param position the position of the activation. + * @param flags currently 0. + * @see IEdgeGestureActivationListener#onEdgeGestureActivation(int, int, int, int) + */ + public abstract void onEdgeGestureActivation(int touchX, int touchY, EdgeGesturePosition position, int flags); + + /** + * After being activated, this allows the edge gesture control to steal focus from the current + * window. + * + * @see IEdgeGestureHostCallback#gainTouchFocus(IBinder) + */ + public boolean gainTouchFocus(IBinder applicationWindowToken) { + try { + return mCallback.gainTouchFocus(applicationWindowToken); + } catch (RemoteException e) { + Slog.w(TAG, "gainTouchFocus failed: " + e.getMessage()); + /* fall through */ + } + return false; + } + + public boolean dropEventsUntilLift() { + try { + return mCallback.dropEventsUntilLift(); + } catch (RemoteException e) { + Slog.w(TAG, "dropNextEvents failed: " + e.getMessage()); + /* fall through */ + } + return false; + } + + /** + * Turns listening for edge gesture activation gestures on again, after it was disabled during + * the call to the listener. + * + * @see IEdgeGestureHostCallback#restoreListenerState() + */ + public void restoreListenerState() { + if (DEBUG) { + Slog.d(TAG, "restore listener state: " + Thread.currentThread().getName()); + } + try { + mCallback.restoreListenerState(); + } catch (RemoteException e) { + Slog.w(TAG, "restoreListenerState failed: " + e.getMessage()); + /* fall through */ + } + } + } + + private EdgeGestureManager(IEdgeGestureService ps) { + mPs = ps; + } + + /** + * Gets an instance of the edge gesture manager. + * + * @return The edge gesture manager instance. + * @hide + */ + public static EdgeGestureManager getInstance() { + synchronized (EdgeGestureManager.class) { + if (sInstance == null) { + IBinder b = ServiceManager.getService("edgegestureservice"); + sInstance = new EdgeGestureManager(IEdgeGestureService.Stub.asInterface(b)); + } + return sInstance; + } + } + + /** + * Checks if the edge gesture service is present. + *

+ * Since the service is only started at boot time and is bound to the system server, this + * is constant for the devices up time. + * + * @return {@code true} when the edge gesture service is running on this device. + * @hide + */ + public boolean isPresent() { + return mPs != null; + } + + /** + * Register a listener for edge gesture activation gestures. Initially the listener + * is set to listen for no position. Use updateedge gestureActivationListener() to + * bind the listener to positions. + * + * @param listener is the activation listener. + * @return {@code true} if the registration was successful. + * @hide + */ + public boolean setEdgeGestureActivationListener(EdgeGestureActivationListener listener) { + if (DEBUG) { + Slog.d(TAG, "Set edge gesture activation listener"); + } + if (mPs == null) { + Slog.e(TAG, "Failed to set edge gesture activation listener: Service not present"); + return false; + } + try { + IEdgeGestureHostCallback callback = mPs.registerEdgeGestureActivationListener(listener.mDelegator); + listener.setHostCallback(callback); + return true; + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set edge gesture activation listener: " + e.getMessage()); + return false; + } + } + + /** + * Update the listener to react on gestures in the given positions. + * + * @param listener is a already registered listener. + * @param positions is a bit mask describing the positions to listen to. + * @hide + */ + public void updateEdgeGestureActivationListener(EdgeGestureActivationListener listener, int positions) { + if (DEBUG) { + Slog.d(TAG, "Update edge gesture activation listener: 0x" + Integer.toHexString(positions)); + } + if (mPs == null) { + Slog.e(TAG, "Failed to update edge gesture activation listener: Service not present"); + return; + } + try { + mPs.updateEdgeGestureActivationListener(listener.mDelegator.asBinder(), positions); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to update edge gesture activation listener: " + e.getMessage()); + } + } + +} diff --git a/core/java/android/service/gesture/IEdgeGestureActivationListener.aidl b/core/java/android/service/gesture/IEdgeGestureActivationListener.aidl new file mode 100644 index 0000000000000..0c9b24a8ae3d1 --- /dev/null +++ b/core/java/android/service/gesture/IEdgeGestureActivationListener.aidl @@ -0,0 +1,14 @@ + +package android.service.gesture; + +import android.view.InputEvent; + +/** @hide */ +interface IEdgeGestureActivationListener { + + /** Called when a gesture is detected that fits to the activation gesture. At this point in + * time gesture detection is disabled. Call IEdgeGestureHostCallback.restoreState() to + * recover from this. + */ + oneway void onEdgeGestureActivation(int touchX, int touchY, int positionIndex, int flags); +} \ No newline at end of file diff --git a/core/java/android/service/gesture/IEdgeGestureHostCallback.aidl b/core/java/android/service/gesture/IEdgeGestureHostCallback.aidl new file mode 100644 index 0000000000000..c2615503e4fc3 --- /dev/null +++ b/core/java/android/service/gesture/IEdgeGestureHostCallback.aidl @@ -0,0 +1,20 @@ +package android.service.gesture; + +/** @hide */ +interface IEdgeGestureHostCallback { + + /** After being activated, this allows to steal focus from the current + * window + */ + boolean gainTouchFocus(IBinder windowToken); + + /** Turns listening for activation gestures on again, after it was disabled during + * the call to the listener. + */ + oneway void restoreListenerState(); + + /* + * Tells filter to drop all events till touch up + */ + boolean dropEventsUntilLift(); +} \ No newline at end of file diff --git a/core/java/android/service/gesture/IEdgeGestureService.aidl b/core/java/android/service/gesture/IEdgeGestureService.aidl new file mode 100644 index 0000000000000..342cf71aa32bf --- /dev/null +++ b/core/java/android/service/gesture/IEdgeGestureService.aidl @@ -0,0 +1,20 @@ +package android.service.gesture; + +import android.service.gesture.IEdgeGestureActivationListener; +import android.service.gesture.IEdgeGestureHostCallback; + +/** @hide */ +interface IEdgeGestureService { + + /** Register a listener for activation gestures. Initially the listener + * is set to listen for no position. Use updateEdgeGestureActivationListener() to + * bind the listener to positions. + * Use the returned IEdgeGestureHostCallback to manipulate the state after activation. + */ + IEdgeGestureHostCallback registerEdgeGestureActivationListener(in IEdgeGestureActivationListener listener); + + /** Update the listener to react on gestures in the given positions. + */ + void updateEdgeGestureActivationListener(in IBinder listener, int positionFlags); + +} \ No newline at end of file diff --git a/core/java/android/service/gesture/package.html b/core/java/android/service/gesture/package.html new file mode 100644 index 0000000000000..c9f96a66ab3bc --- /dev/null +++ b/core/java/android/service/gesture/package.html @@ -0,0 +1,5 @@ + + +{@hide} + + diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index a8b07c98a25c6..e3e7dfad49c81 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -310,6 +310,15 @@ public final class InputDevice implements Parcelable { */ public static final int SOURCE_NAVIGATION_BAR = 0x04000000 | SOURCE_CLASS_BUTTON; + /** + * The input source is a custom virtual key event sent programmatically to emulate different events. + * + * The key requested is different from the actual key's event. + * + * @hide + */ + public static final int SOURCE_CUSTOM = 0x08000000 | SOURCE_CLASS_BUTTON; + /** * A special input source constant that is used when filtering input devices * to match devices that provide any type of input source. diff --git a/core/java/com/android/internal/util/gesture/EdgeGesturePosition.java b/core/java/com/android/internal/util/gesture/EdgeGesturePosition.java new file mode 100644 index 0000000000000..01cfdeae54fbe --- /dev/null +++ b/core/java/com/android/internal/util/gesture/EdgeGesturePosition.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project (Jens Doll) + * + * 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.internal.util.gesture; + +/** + * Defines the positions in which gestures may be recognized by the + * edge gesture service. + * This defines an index and an flag for each position. + */ +public enum EdgeGesturePosition { + LEFT(0, 0), + BOTTOM(1, 1), + RIGHT(2, 1), + TOP(3, 0); + + EdgeGesturePosition(int index, int factor) { + INDEX = index; + FLAG = (0x01< + * Positions are specified by {@code EdgeGesturePosition.FLAG}. + */ + public static final int POSITION_MASK = 0x0000001f; + + /** + * Mask for coding sensitivity within the flags of + * {@code updateEdgeGestureActivationListener()}. + *

+ * Sensitivity influences the speed of the swipe, the trigger area, and trigger distance that + * is needed to activate the edge gesture. + */ + public static final int SENSITIVITY_MASK = 0x70000000; + + /** + * Number of bits to shift left, to get a integer within the {@link #SENSITIVITY_MASK}. + */ + public static final int SENSITIVITY_SHIFT = 28; + + /** + * No sensitivity specified at all, the service may choose a sensitivity level on its own. + */ + public static final int SENSITIVITY_NONE = 0; + + /** + * Default sensitivity, picked by the edge gesture service automatically. + */ + public static final int SENSITIVITY_DEFAULT = 2; + + /** + * Lowest valid sensitivity value. + */ + public static final int SENSITIVITY_LOWEST = 1; + + /** + * Highest sensitivity value. + */ + public static final int SENSITIVITY_HIGHEST = 4; + + /** + * Do not cut 10% area on th edges + */ + public static final int UNRESTRICTED = 0x10; + + /** + * This listener does not likes enabling/disabling filter + * because it interrupt in motion events. + */ + public static final int LONG_LIVING = 0x20; + +} diff --git a/core/res/res/values/aos_config.xml b/core/res/res/values/aos_config.xml index c370c3f4bdff9..cd9c82a74ae8f 100644 --- a/core/res/res/values/aos_config.xml +++ b/core/res/res/values/aos_config.xml @@ -138,4 +138,9 @@ false + + + + + diff --git a/core/res/res/values/aos_dimens.xml b/core/res/res/values/aos_dimens.xml new file mode 100644 index 0000000000000..3896e338e5301 --- /dev/null +++ b/core/res/res/values/aos_dimens.xml @@ -0,0 +1,12 @@ + + + + 12dp + + + 15dp + + + 12dp + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 8d5481228a033..45a79809e2083 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -271,6 +271,7 @@ import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.statusbar.screen_gestures.ScreenGesturesController; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout .OnChildLocationsChangedListener; @@ -418,6 +419,9 @@ public class StatusBar extends SystemUI implements DemoMode, */ private static final float SRC_MIN_ALPHA = 0.002f; + private static final String EDGE_GESTURES_ENABLED = + Settings.Secure.EDGE_GESTURES_ENABLED; + static { boolean onlyCoreApps; boolean freeformWindowManagement; @@ -541,6 +545,10 @@ public class StatusBar extends SystemUI implements DemoMode, private int mAmbientMediaPlaying; + // Full Screen Gestures + protected ScreenGesturesController gesturesController; + private boolean mEdgeGesturesEnabled; + // Tracking finger for opening/closing. boolean mTracking; @@ -6669,6 +6677,10 @@ void observe() { resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.LOCKSCREEN_DATE_SELECTION), false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.EDGE_GESTURES_ENABLED), + false, this, UserHandle.USER_ALL); + update(); } @Override @@ -6739,6 +6751,9 @@ public void onChange(boolean selfChange, Uri uri) { uri.equals(Settings.System.getUriFor(Settings.System.LOCKSCREEN_CLOCK_SELECTION)) || uri.equals(Settings.System.getUriFor(Settings.System.LOCKSCREEN_DATE_SELECTION))) { updateKeyguardStatusSettings(); + } else if (uri.equals(Settings.Secure.getUriFor( + Settings.Secure.EDGE_GESTURES_ENABLED))) { + updateEdgeGestures(); } } @@ -6755,6 +6770,7 @@ public void update() { updateRecentsMode(); updateTickerAnimation(); updateKeyguardStatusSettings(); + updateEdgeGestures(); } } @@ -8716,4 +8732,20 @@ public void startAssist(Bundle args) { mNavigationBar.getBarTransitions().setAutoDim(true); } }; + + public void updateEdgeGestures() { + Log.d(TAG, "updateEdgeGestures: Updating edge gestures"); + boolean enabled = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.EDGE_GESTURES_ENABLED, 0, mCurrentUserId) == 1; + if (enabled) { + if (gesturesController == null) { + gesturesController = new ScreenGesturesController(mContext, mWindowManager, this); + } + gesturesController.reorient(); + } else if (!enabled && gesturesController != null) { + gesturesController.stop(); + gesturesController = null; + } + } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesController.java b/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesController.java new file mode 100644 index 0000000000000..b33fe689bac5e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesController.java @@ -0,0 +1,176 @@ +package com.android.systemui.statusbar.screen_gestures; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.hardware.input.InputManager; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; +import android.os.UserHandle; +import android.os.Vibrator; +import android.provider.Settings; +import android.service.gesture.EdgeGestureManager; +import android.util.Log; +import android.view.Gravity; +import android.view.InputDevice; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.ViewGroup; +import android.view.WindowManager; + +import com.android.internal.util.gesture.EdgeGesturePosition; +import com.android.internal.util.gesture.EdgeServiceConstants; +import com.android.systemui.statusbar.phone.StatusBar; + +/** + * Created by arasthel on 15/02/18. + */ + +public class ScreenGesturesController { + + public static final boolean DEBUG = false; + + private static final String TAG = "ScreenGesturesControlle"; + + private Context context; + private WindowManager windowManager; + private StatusBar statusBar; + + private ScreenGesturesView screenGesturesView; + + private EdgeGestureManager edgeGestureManager = EdgeGestureManager.getInstance(); + + private EdgeGestureManager.EdgeGestureActivationListener gestureManagerListener = new EdgeGestureManager.EdgeGestureActivationListener() { + + @Override + public void onEdgeGestureActivation(int touchX, int touchY, EdgeGesturePosition position, int flags) { + if (DEBUG) Log.d(TAG, "onEdgeGestureActivation: Starting gesture"); + final ScreenGesturesView gesturesView = screenGesturesView; + + if (gesturesView != null) { + boolean startGesture = true; + String backSettingsId = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? + Settings.Secure.EDGE_GESTURES_BACK_EDGES : + Settings.Secure.EDGE_GESTURES_LANDSCAPE_BACK_EDGES; + int backGestureEdgesFlag = 0; + int percent = 0; + try { + backGestureEdgesFlag = Settings.Secure.getIntForUser(context.getContentResolver(), + backSettingsId, + UserHandle.USER_CURRENT); + percent = Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.EDGE_GESTURES_BACK_SCREEN_PERCENT, + UserHandle.USER_CURRENT); + } catch (Settings.SettingNotFoundException e) { + e.printStackTrace(); + } + if ((position.FLAG & backGestureEdgesFlag) != 0) { + Point displaySize = new Point(); + windowManager.getDefaultDisplay().getSize(displaySize); + startGesture = touchY > (percent*displaySize.y)/100; + } + + if (startGesture) { + gesturesView.startGesture(touchX, touchY, position); + + handler.post(() -> { + gainTouchFocus(gesturesView.getWindowToken()); + gestureManagerListener.restoreListenerState(); + }); + } else { + gestureManagerListener.restoreListenerState(); + } + } else { + gestureManagerListener.restoreListenerState(); + } + + } + }; + + private ScreenGesturesView.OnGestureCompletedListener onGestureCompletedListener = gestureType -> { + switch (gestureType) { + case ScreenGesturesView.GestureType.HOME: + injectKeyCode(KeyEvent.KEYCODE_HOME); + break; + case ScreenGesturesView.GestureType.BACK: + injectKeyCode(KeyEvent.KEYCODE_BACK); + break; + case ScreenGesturesView.GestureType.RECENTS: + injectKeyCode(KeyEvent.KEYCODE_APP_SWITCH); + break; + default: + Log.e(TAG, "Unknown event"); + break; + } + + gestureManagerListener.restoreListenerState(); + + //setupEdgeGestureManager(); + }; + + private Handler handler = new Handler(Looper.getMainLooper()); + + public ScreenGesturesController(Context context, WindowManager windowManager, StatusBar statusBar) { + this.context = context; + this.windowManager = windowManager; + this.statusBar = statusBar; + + edgeGestureManager.setEdgeGestureActivationListener(gestureManagerListener); + setupEdgeGestureManager(); + + screenGesturesView = new ScreenGesturesView(context); + WindowManager.LayoutParams params = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, + PixelFormat.TRANSLUCENT); + windowManager.addView(screenGesturesView, params); + + screenGesturesView.setOnGestureCompletedListener(onGestureCompletedListener); + } + + public void reorient() { + setupEdgeGestureManager(); + } + + public void stop() { + gestureManagerListener.restoreListenerState(); + edgeGestureManager.updateEdgeGestureActivationListener(gestureManagerListener, 0); + windowManager.removeView(screenGesturesView); + screenGesturesView = null; + } + + private void setupEdgeGestureManager() { + int sensitivity = EdgeServiceConstants.SENSITIVITY_HIGHEST; + + int edges = (EdgeGesturePosition.BOTTOM.FLAG | EdgeGesturePosition.RIGHT.FLAG | EdgeGesturePosition.LEFT.FLAG); + edgeGestureManager.updateEdgeGestureActivationListener(gestureManagerListener, + sensitivity << EdgeServiceConstants.SENSITIVITY_SHIFT + | edges + | EdgeServiceConstants.LONG_LIVING + | EdgeServiceConstants.UNRESTRICTED); + } + + private void injectKeyCode(int keyCode) { + if (DEBUG) Log.d(TAG, "injectKeyCode: Injecting: " + String.valueOf(keyCode)); + + InputManager inputManager = InputManager.getInstance(); + long now = SystemClock.uptimeMillis(); + + KeyEvent downEvent = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0, + KeyCharacterMap.VIRTUAL_KEYBOARD, 0, + KeyEvent.FLAG_FROM_SYSTEM, InputDevice.SOURCE_CUSTOM); + + KeyEvent upEvent = KeyEvent.changeAction(downEvent, KeyEvent.ACTION_UP); + + inputManager.injectInputEvent(downEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + inputManager.injectInputEvent(upEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesView.java b/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesView.java new file mode 100644 index 0000000000000..31981871203d9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesView.java @@ -0,0 +1,222 @@ +package com.android.systemui.statusbar.screen_gestures; + +import android.content.Context; +import android.content.res.Configuration; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.os.Vibrator; +import android.provider.Settings; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; + +import com.android.internal.util.gesture.EdgeGesturePosition; + +/** + * Created by arasthel on 15/02/18. + */ + +public class ScreenGesturesView extends View { + + public static final boolean DEBUG = false; + + public static final int SIGNIFICANT_MOVE = 10; + + private static final String TAG = "ScreenGesturesView"; + + public static class GestureType { + public static final int NONE = 0; + public static final int HOME = 1; + public static final int BACK = 1 << 1; + public static final int RECENTS = 1 << 2; + } + + private OnGestureCompletedListener onGestureCompletedListener; + + private int initialX = -1; + private int initialY = -1; + + private int lastX = -1; + private int lastY = -1; + + private int possibleGestures = GestureType.NONE; + + private Vibrator vibrator; + + private Handler handler = new Handler(Looper.getMainLooper()); + + public ScreenGesturesView(Context context) { + super(context); + } + + public ScreenGesturesView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ScreenGesturesView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public ScreenGesturesView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + setVisibility(View.GONE); + + vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); + } + + public void startGesture(int initialX, int initialY, EdgeGesturePosition position) { + if (DEBUG) Log.d(TAG, "startGesture: Gesture started"); + + this.initialX = initialX; + this.initialY = initialY; + + this.lastX = initialX; + this.lastY = initialY; + + String backSettingsId = getContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? + Settings.Secure.EDGE_GESTURES_BACK_EDGES : + Settings.Secure.EDGE_GESTURES_LANDSCAPE_BACK_EDGES; + int backGestureEdgesFlag = Settings.Secure.getIntForUser(getContext().getContentResolver(), + backSettingsId, + 0, + UserHandle.USER_CURRENT);; + + if ((position.FLAG & backGestureEdgesFlag) != 0) { + possibleGestures = GestureType.BACK; + } else if ((position.FLAG & EdgeGesturePosition.BOTTOM.FLAG) != 0) { + possibleGestures = GestureType.HOME | GestureType.RECENTS; + } else { + if (onGestureCompletedListener != null) { + onGestureCompletedListener.onGestureCompleted(GestureType.NONE); + } + return; + } + + setVisibility(View.VISIBLE); + } + + private int getFeedbackStrength() { + try { + return Settings.Secure.getInt(getContext().getContentResolver(), Settings.Secure.EDGE_GESTURES_FEEDBACK_DURATION); + } catch (Settings.SettingNotFoundException exception) { + return 100; + } + } + + private int getLongpressDuration() { + try { + return Settings.Secure.getInt(getContext().getContentResolver(), Settings.Secure.EDGE_GESTURES_LONG_PRESS_DURATION); + } catch (Settings.SettingNotFoundException exception) { + return 500; + } + } + + private void stopGesture(int posX, int posY) { + if (DEBUG) Log.d(TAG, "stopGesture: Gesture stopped"); + + stopLongPress(); + + if (onGestureCompletedListener == null) return; + + if (DEBUG) Log.d(TAG, "stopGesture: Initial x: " + String.valueOf(initialX) + ", final x: " + String.valueOf(posX)); + if (DEBUG) Log.d(TAG, "stopGesture: Initial y: " + String.valueOf(initialY) + ", final y: " + String.valueOf(posY)); + + final int threshold = 20; + boolean canSendHome = (possibleGestures & GestureType.HOME) != 0; + if (canSendHome && (posY - initialY < -threshold)) { + if (DEBUG) Log.d(TAG, "stopGesture: Home"); + vibrator.vibrate(getFeedbackStrength()); + onGestureCompletedListener.onGestureCompleted(GestureType.HOME); + return; + } + + boolean canSendBack = (possibleGestures & GestureType.BACK) != 0; + if (canSendBack && (Math.abs(posX - initialX) > threshold)) { + if (DEBUG) Log.d(TAG, "stopGesture: Back"); + vibrator.vibrate(getFeedbackStrength()); + onGestureCompletedListener.onGestureCompleted(GestureType.BACK); + return; + } + + if (DEBUG) Log.d(TAG, "stopGesture: None"); + onGestureCompletedListener.onGestureCompleted(GestureType.NONE); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + if (DEBUG) Log.d(TAG, "onTouchEvent: DOWN"); + return true; + case MotionEvent.ACTION_MOVE: + if (DEBUG) Log.d(TAG, "onTouchEvent: MOVE"); + int x = (int) event.getX(); + int y = (int) event.getY(); + if (Math.abs(x - lastX) > SIGNIFICANT_MOVE || Math.abs(y - lastY) > SIGNIFICANT_MOVE) { + stopLongPress(); + startLongPress(); + } + + lastX = x; + lastY = y; + return true; + case MotionEvent.ACTION_UP: + if (DEBUG) Log.d(TAG, "onTouchEvent: UP"); + if (possibleGestures != GestureType.NONE) { + stopGesture((int) event.getX(), (int) event.getY()); + setVisibility(View.GONE); + } + return true; + case MotionEvent.ACTION_CANCEL: + if (DEBUG) Log.d(TAG, "onTouchEvent: DOWN"); + stopGesture((int) event.getX(), (int) event.getY()); + setVisibility(View.GONE); + return true; + } + return false; + } + + private void startLongPress() { + if (DEBUG) Log.d(TAG, "startLongPress: scheduling long press"); + handler.postDelayed(longPressRunnable, getLongpressDuration()); + } + + private void stopLongPress() { + if (DEBUG) Log.d(TAG, "stopLongPress: cancellling long press"); + handler.removeCallbacks(longPressRunnable); + } + + private Runnable longPressRunnable = new Runnable() { + @Override + public void run() { + boolean canSendRecents = (possibleGestures & GestureType.RECENTS) != 0; + if (canSendRecents) { + possibleGestures = GestureType.NONE; + setVisibility(View.GONE); + if (onGestureCompletedListener != null) { + onGestureCompletedListener.onGestureCompleted(GestureType.RECENTS); + } + vibrator.vibrate(getFeedbackStrength()); + } + } + }; + + public OnGestureCompletedListener getOnGestureCompletedListener() { + return onGestureCompletedListener; + } + + public void setOnGestureCompletedListener(OnGestureCompletedListener onGestureCompletedListener) { + this.onGestureCompletedListener = onGestureCompletedListener; + } + + interface OnGestureCompletedListener { + void onGestureCompleted(int gestureType); + } +} diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 9ed35690dec15..d6bc46d29e9f3 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -204,6 +204,7 @@ import android.service.dreams.DreamManagerInternal; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; +import android.service.gesture.EdgeGestureManager; import android.service.vr.IPersistentVrStateCallbacks; import android.speech.RecognizerIntent; import android.telecom.TelecomManager; @@ -256,6 +257,8 @@ import com.android.internal.policy.IShortcutService; import com.android.internal.policy.PhoneWindow; import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.util.gesture.EdgeGesturePosition; +import com.android.internal.util.gesture.EdgeServiceConstants; import com.android.internal.util.ScreenShapeHelper; import com.android.internal.utils.du.ActionHandler; import com.android.internal.utils.du.DUActionUtils; @@ -1222,6 +1225,9 @@ void observe() { resolver.registerContentObserver(Settings.Global.getUriFor( Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED), false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.USE_EDGE_SERVICE_FOR_GESTURES), false, this, + UserHandle.USER_ALL); updateSettings(); } @@ -1316,6 +1322,67 @@ public void onBarVisibilityChanged(boolean visible) { private SystemGesturesPointerEventListener mSystemGestures; + private EdgeGestureManager.EdgeGestureActivationListener mEdgeGestureActivationListener + = new EdgeGestureManager.EdgeGestureActivationListener() { + + @Override + public void onEdgeGestureActivation(int touchX, int touchY, + EdgeGesturePosition position, int flags) { + WindowState target = null; + + if (position == EdgeGesturePosition.TOP) { + target = mStatusBar; + } else if ((position == EdgeGesturePosition.BOTTOM) && (mNavigationBarPosition == NAV_BAR_BOTTOM)) { + target = mNavigationBar; + } else if ((position == EdgeGesturePosition.RIGHT) && (mNavigationBarPosition == NAV_BAR_RIGHT)) { + target = mNavigationBar; + } else if ((position == EdgeGesturePosition.LEFT) && (mNavigationBarPosition == NAV_BAR_LEFT)) { + target = mNavigationBar; + } + + if (target != null) { + requestTransientBars(target); + dropEventsUntilLift(); + mEdgeListenerActivated = true; + } else { + restoreListenerState(); + } + } + }; + private EdgeGestureManager mEdgeGestureManager = null; + private int mLastEdgePositions = 0; + private boolean mEdgeListenerActivated = false; + private boolean mUsingEdgeGestureServiceForGestures = false; + + private void updateEdgeGestureListenerState() { + int flags = 0; + if (mUsingEdgeGestureServiceForGestures) { + flags = EdgeServiceConstants.LONG_LIVING | EdgeServiceConstants.UNRESTRICTED; + if (mStatusBar != null && !mStatusBar.isVisibleLw()) { + flags |= EdgeGesturePosition.TOP.FLAG; + } + if (mNavigationBar != null && !mNavigationBar.isVisibleLw() + && !isStatusBarKeyguard()) { + if (mNavigationBarPosition == NAV_BAR_BOTTOM) { + flags |= EdgeGesturePosition.BOTTOM.FLAG; + } else if (mNavigationBarPosition == NAV_BAR_RIGHT){ + flags |= EdgeGesturePosition.RIGHT.FLAG; + } else if (mNavigationBarPosition == NAV_BAR_LEFT) { + flags |= EdgeGesturePosition.LEFT.FLAG; + } + } + } + if (mEdgeListenerActivated) { + mEdgeGestureActivationListener.restoreListenerState(); + mEdgeListenerActivated = false; + } + if (flags != mLastEdgePositions) { + mEdgeGestureManager.updateEdgeGestureActivationListener(mEdgeGestureActivationListener, + flags); + mLastEdgePositions = flags; + } + } + IStatusBarService getStatusBarService() { synchronized (mServiceAquireLock) { if (mStatusBarService == null) { @@ -2746,6 +2813,19 @@ public void updateSettings() { mUserRotationAngles = Settings.System.getInt(resolver, Settings.System.ACCELEROMETER_ROTATION_ANGLES, -1); + final boolean useEdgeService = Settings.System.getIntForUser(resolver, + Settings.System.USE_EDGE_SERVICE_FOR_GESTURES, 1, UserHandle.USER_CURRENT) == 1; + if (useEdgeService ^ mUsingEdgeGestureServiceForGestures && mSystemReady) { + if (!mUsingEdgeGestureServiceForGestures && useEdgeService) { + mUsingEdgeGestureServiceForGestures = true; + mWindowManagerFuncs.unregisterPointerEventListener(mSystemGestures); + } else if (mUsingEdgeGestureServiceForGestures && !useEdgeService) { + mUsingEdgeGestureServiceForGestures = false; + mWindowManagerFuncs.registerPointerEventListener(mSystemGestures); + } + updateEdgeGestureListenerState(); + } + if (mSystemReady) { int pointerLocation = Settings.System.getIntForUser(resolver, Settings.System.POINTER_LOCATION, 0, UserHandle.USER_CURRENT); @@ -4798,6 +4878,8 @@ public int adjustSystemUiVisibilityLw(int visibility) { mStatusBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility); mNavigationBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility); + updateEdgeGestureListenerState(); + // Reset any bits in mForceClearingStatusBarVisibility that // are now clear. mResettingSystemUiFlags &= visibility; @@ -6187,6 +6269,7 @@ public int finishPostLayoutPolicyLw() { // update since mAllowLockscreenWhenOn might have changed updateLockScreenTimeout(); + updateEdgeGestureListenerState(); return changes; } @@ -8248,6 +8331,13 @@ public void systemReady() { mANBIHandler = new ANBIHandler(mContext); + mEdgeGestureManager = EdgeGestureManager.getInstance(); + mEdgeGestureManager.setEdgeGestureActivationListener(mEdgeGestureActivationListener); + + // Ensure observe happens in systemReady() since we need + // LineageHardwareService to be up and running + mSettingsObserver.observe(); + readCameraLensCoverState(); updateUiMode(); synchronized (mLock) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 7d9517a22a924..d55ce5a4e9b62 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -91,6 +91,7 @@ import com.android.server.os.SchedulingPolicyService; import com.android.server.pocket.PocketService; import com.android.server.pm.BackgroundDexOptService; +import com.android.server.gesture.EdgeGestureService; import com.android.server.pm.Installer; import com.android.server.pm.LauncherAppsService; import com.android.server.pm.OtaDexoptService; @@ -909,6 +910,7 @@ private void startOtherServices() { ILockSettings lockSettings = null; MediaRouterService mediaRouter = null; GestureService gestureService = null; + EdgeGestureService edgeGestureService = null; // Bring up services needed for UI. if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) { @@ -1540,6 +1542,14 @@ private void startOtherServices() { Slog.i(TAG, "Starting PocketService"); mSystemServiceManager.startService(PocketService.class); + traceBeginAndSlog("EdgeGestureService"); + try { + edgeGestureService = new EdgeGestureService(context, inputManager); + ServiceManager.addService("edgegestureservice", edgeGestureService); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting EdgeGesture service", e); + } + traceEnd(); } if (!disableNonCoreServices && !disableMediaProjection) { @@ -1690,11 +1700,11 @@ private void startOtherServices() { mSystemServiceManager.setSafeMode(safeMode); - if (gestureService != null) { + if (edgeGestureService != null) { try { - gestureService.systemReady(); + edgeGestureService.systemReady(); } catch (Throwable e) { - reportWtf("making Gesture Sensor Service ready", e); + reportWtf("making EdgeGesture service ready", e); } } diff --git a/services/java/com/android/server/gesture/EdgeGestureInputFilter.java b/services/java/com/android/server/gesture/EdgeGestureInputFilter.java new file mode 100644 index 0000000000000..c42b7d098b45d --- /dev/null +++ b/services/java/com/android/server/gesture/EdgeGestureInputFilter.java @@ -0,0 +1,539 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project (Jens Doll) + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.server.gesture; + +import android.content.Context; +import android.content.res.Resources; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.Trace; +import android.util.Slog; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.IInputFilter; +import android.view.IInputFilterHost; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.MotionEvent; +import android.view.WindowManagerPolicy; +import android.view.MotionEvent.PointerCoords; +import android.view.MotionEvent.PointerProperties; + +import com.android.internal.R; +import com.android.internal.util.gesture.EdgeGesturePosition; +import com.android.server.gesture.EdgeGestureTracker.OnActivationListener; + +import java.io.PrintWriter; + +/** + * A simple input filter, that listens for edge swipe gestures in the motion event input + * stream. + *

+ * There are 5 distinct states of this filter. + * 1) LISTEN: + * mTracker.active == false + * All motion events are passed through. If a ACTION_DOWN within a gesture trigger area happen + * switch to DETECTING. + * 2) DETECTING: + * mTracker.active == true + * All events are buffered now, and the gesture is checked by mTracker. If mTracker rejects + * the gesture (hopefully as fast as possible) all cached events will be flushed out and the + * filter falls back to LISTEN. + * If mTracker accepts the gesture, clear all cached events and go to LOCKED. + * 3) LOCKED: + * mTracker.active == false + * All events will be cached until the state changes to SYNTHESIZE through a filter + * unlock event. If there is a ACTION_UP, _CANCEL or any PointerId differently to the last + * event seen when mTracker accepted the gesture, we flush all events and go to LISTEN. + * 4) SYNTHESIZE: + * The first motion event found will be turned into a ACTION_DOWN event, all previous events + * will be discarded. + * 5) POSTSYNTHESIZE: + * mSyntheticDownTime != -1 + * All following events will have the down time set to the synthesized ACTION_DOWN event time + * until an ACTION_UP or ACTION_CANCEL is encountered and the state is reset to LISTEN. + * 6) DROP: + * All following events will be discarded. If there is an ACTION_UP, _CANCEL + * we go to LISTEN state. + *

+ * If you are reading this within Java Doc, you are doing something wrong ;) + */ +public class EdgeGestureInputFilter implements IInputFilter { + /* WARNING!! The IInputFilter interface is used directly, there is no Binder between this and + * the InputDispatcher. + * This is fine, because it prevents unnecessary parceling, but beware: + * This means we are running on the dispatch or listener thread of the input dispatcher. Every + * cycle we waste here adds to the overall input latency. + */ + private static final String TAG = "EdgeGestureInputFilter"; + private static final boolean DEBUG = false; + private static final boolean DEBUG_INPUT = false; + // TODO: Should be turned off in final commit + private static final boolean SYSTRACE = false; + + private final Handler mHandler; + + private IInputFilterHost mHost = null; // dispatcher thread + + private static final class MotionEventInfo { + private static final int MAX_POOL_SIZE = 16; + + private static final Object sLock = new Object(); + private static MotionEventInfo sPool; + private static int sPoolSize; + + private boolean mInPool; + + public static MotionEventInfo obtain(MotionEvent event, int policyFlags) { + synchronized (sLock) { + MotionEventInfo info; + if (sPoolSize > 0) { + sPoolSize--; + info = sPool; + sPool = info.next; + info.next = null; + info.mInPool = false; + } else { + info = new MotionEventInfo(); + } + info.initialize(event, policyFlags); + return info; + } + } + + private void initialize(MotionEvent event, int policyFlags) { + this.event = MotionEvent.obtain(event); + this.policyFlags = policyFlags; + cachedTimeMillis = SystemClock.uptimeMillis(); + } + + public void recycle() { + synchronized (sLock) { + if (mInPool) { + throw new IllegalStateException("Already recycled."); + } + clear(); + if (sPoolSize < MAX_POOL_SIZE) { + sPoolSize++; + next = sPool; + sPool = this; + mInPool = true; + } + } + } + + private void clear() { + event.recycle(); + event = null; + policyFlags = 0; + } + + public MotionEventInfo next; + public MotionEvent event; + public int policyFlags; + public long cachedTimeMillis; + } + private final Object mLock = new Object(); + private MotionEventInfo mMotionEventQueue; // guarded by mLock + private MotionEventInfo mMotionEventQueueTail; // guarded by mLock + /* DEBUG */ + private int mMotionEventQueueCountDebug; // guarded by mLock + + private int mDeviceId; // dispatcher only + private enum State { + LISTEN, DETECTING, LOCKED, SYNTHESIZE, POSTSYNTHESIZE, DROP; + } + private State mState = State.LISTEN; // guarded by mLock + private EdgeGestureTracker mTracker; // guarded by mLock + private volatile int mPositions; // written by handler / read by dispatcher + private volatile int mSensitivity; // written by handler / read by dispatcher + + // only used by dispatcher + private long mSyntheticDownTime = -1; + private PointerCoords[] mTempPointerCoords = new PointerCoords[1]; + private PointerProperties[] mTempPointerProperties = new PointerProperties[1]; + + public EdgeGestureInputFilter(Context context, Handler handler) { + mHandler = handler; + + final Resources res = context.getResources(); + mTracker = new EdgeGestureTracker(res.getDimensionPixelSize( + R.dimen.edge_gesture_trigger_thickness), + res.getDimensionPixelSize(R.dimen.edge_gesture_trigger_distance), + res.getDimensionPixelSize(R.dimen.edge_gesture_perpendicular_distance)); + mTracker.setOnActivationListener(new OnActivationListener() { + public void onActivation(MotionEvent event, int touchX, int touchY, EdgeGesturePosition position) { + // mLock is held by #processMotionEvent + mHandler.obtainMessage(EdgeGestureService.MSG_EDGE_GESTURE_ACTIVATION, + touchX, touchY, position).sendToTarget(); + mState = State.LOCKED; + } + }); + mTempPointerCoords[0] = new PointerCoords(); + mTempPointerProperties[0] = new PointerProperties(); + } + + // called from handler thread (lock taken) + public void updateDisplay(Display display, DisplayInfo displayInfo) { + synchronized (mLock) { + mTracker.updateDisplay(display); + } + } + + // called from handler thread (lock taken) + public void updatePositions(int positions) { + mPositions = positions; + } + + // called from handler thread (lock taken) + public void updateSensitivity(int sensitivity) { + mSensitivity = sensitivity; + } + + // called from handler thread + public boolean unlockFilter() { + synchronized (mLock) { + if (mState == State.LOCKED) { + mState = State.SYNTHESIZE; + return true; + } + } + return false; + } + + public boolean dropSequence() { + synchronized (mLock) { + if (mState == State.LOCKED) { + mState = State.DROP; + return true; + } + } + return false; + } + + /** + * Called to enqueue the input event for filtering. + * The event must be recycled after the input filter processed it. + * This method is guaranteed to be non-reentrant. + * + * @see InputFilter#filterInputEvent(InputEvent, int) + * @param event The input event to enqueue. + */ + // called by the input dispatcher thread + public void filterInputEvent(InputEvent event, int policyFlags) throws RemoteException { + if (SYSTRACE) { + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "filterInputEvent"); + } + try { + if (((event.getSource() & InputDevice.SOURCE_TOUCHSCREEN) + != InputDevice.SOURCE_TOUCHSCREEN) + || !(event instanceof MotionEvent)) { + sendInputEvent(event, policyFlags); + return; + } + if (DEBUG_INPUT) { + Slog.d(TAG, "Received event: " + event + ", policyFlags=0x" + + Integer.toHexString(policyFlags)); + } + MotionEvent motionEvent = (MotionEvent) event; + final int deviceId = event.getDeviceId(); + if (deviceId != mDeviceId) { + processDeviceSwitch(deviceId, motionEvent, policyFlags); + } else { + if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) { + synchronized (mLock) { + clearAndResetStateLocked(false, true); + } + } + processMotionEvent(motionEvent, policyFlags); + } + } finally { + event.recycle(); + if (SYSTRACE) { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); + } + } + } + + private void processDeviceSwitch(int deviceId, MotionEvent motionEvent, int policyFlags) { + if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { + mDeviceId = deviceId; + synchronized (mLock) { + clearAndResetStateLocked(true, false); + processMotionEvent(motionEvent, policyFlags); + } + } else { + sendInputEvent(motionEvent, policyFlags); + } + } + + private void processMotionEvent(MotionEvent motionEvent, int policyFlags) { + final int action = motionEvent.getActionMasked(); + + synchronized (mLock) { + switch (mState) { + case LISTEN: + if (action == MotionEvent.ACTION_DOWN) { + boolean hit = mPositions != 0 + && mTracker.start(motionEvent, mPositions, mSensitivity); + if (DEBUG) Slog.d(TAG, "start:" + hit); + if (hit) { + // cache the down event + cacheDelayedMotionEventLocked(motionEvent, policyFlags); + mState = State.DETECTING; + return; + } + } + sendInputEvent(motionEvent, policyFlags); + break; + case DETECTING: + cacheDelayedMotionEventLocked(motionEvent, policyFlags); + if (action == MotionEvent.ACTION_MOVE) { + if (mTracker.move(motionEvent)) { + // return: the tracker is either detecting or triggered onActivation + return; + } + } + if (DEBUG) { + Slog.d(TAG, "move: reset!"); + } + clearAndResetStateLocked(false, true); + break; + case LOCKED: + cacheDelayedMotionEventLocked(motionEvent, policyFlags); + if (action != MotionEvent.ACTION_MOVE) { + clearAndResetStateLocked(false, true); + } + break; + case SYNTHESIZE: + if (action == MotionEvent.ACTION_MOVE) { + clearDelayedMotionEventsLocked(); + sendSynthesizedMotionEventLocked(motionEvent, policyFlags); + mState = State.POSTSYNTHESIZE; + } else { + // This is the case where a race condition caught us: We already + // returned the handler thread that it is all right to call + // #gainTouchFocus(), but apparently this was wrong, as the gesture + // was canceled now. + clearAndResetStateLocked(false, true); + } + break; + case POSTSYNTHESIZE: + motionEvent.setDownTime(mSyntheticDownTime); + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + mState = State.LISTEN; + mSyntheticDownTime = -1; + } + sendInputEvent(motionEvent, policyFlags); + break; + case DROP: + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + clearDelayedMotionEventsLocked(); + mState = State.LISTEN; + } + break; + } + } + } + + private void clearAndResetStateLocked(boolean force, boolean shift) { + // ignore soft reset in POSTSYNTHESIZE, because we need to tamper with + // the event stream and going to LISTEN after an ACTION_UP anyway + if (!force && (mState == State.POSTSYNTHESIZE)) { + return; + } + switch (mState) { + case LISTEN: + // this is a nop + break; + case DETECTING: + mTracker.reset(); + // intentionally no break here + case LOCKED: + case SYNTHESIZE: + sendDelayedMotionEventsLocked(shift); + break; + case POSTSYNTHESIZE: + // hard reset (this will break the event stream) + Slog.w(TAG, "Quit POSTSYNTHESIZE without ACTION_UP from ACTION_DOWN at " + + mSyntheticDownTime); + mSyntheticDownTime = -1; + break; + } + // if there are future events that need to be tampered with, goto POSTSYNTHESIZE + mState = mSyntheticDownTime == -1 ? State.LISTEN : State.POSTSYNTHESIZE; + } + + private void sendInputEvent(InputEvent event, int policyFlags) { + try { + mHost.sendInputEvent(event, policyFlags); + } catch (RemoteException e) { + /* ignore */ + } + } + + private void cacheDelayedMotionEventLocked(MotionEvent event, int policyFlags) { + MotionEventInfo info = MotionEventInfo.obtain(event, policyFlags); + if (mMotionEventQueue == null) { + mMotionEventQueue = info; + } else { + mMotionEventQueueTail.next = info; + } + mMotionEventQueueTail = info; + mMotionEventQueueCountDebug++; + if (SYSTRACE) { + Trace.traceCounter(Trace.TRACE_TAG_INPUT, "meq", mMotionEventQueueCountDebug); + } + } + + private void sendDelayedMotionEventsLocked(boolean shift) { + while (mMotionEventQueue != null) { + MotionEventInfo info = mMotionEventQueue; + mMotionEventQueue = info.next; + + if (DEBUG) { + Slog.d(TAG, "Replay event: " + info.event); + } + mMotionEventQueueCountDebug--; + if (SYSTRACE) { + Trace.traceCounter(Trace.TRACE_TAG_INPUT, "meq", mMotionEventQueueCountDebug); + } + if (shift) { + final long offset = SystemClock.uptimeMillis() - info.cachedTimeMillis; + if (info.event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mSyntheticDownTime = info.event.getDownTime() + offset; + } + sendMotionEventWithOffsetLocked(info.event, info.policyFlags, mSyntheticDownTime, offset); + if (info.event.getActionMasked() == MotionEvent.ACTION_UP) { + mSyntheticDownTime = -1; + } + } else { + sendInputEvent(info.event, info.policyFlags); + } + info.recycle(); + } + mMotionEventQueueTail = null; + } + + private void clearDelayedMotionEventsLocked() { + while (mMotionEventQueue != null) { + MotionEventInfo next = mMotionEventQueue.next; + mMotionEventQueue.recycle(); + mMotionEventQueue = next; + } + mMotionEventQueueTail = null; + mMotionEventQueueCountDebug = 0; + if (SYSTRACE) { + Trace.traceCounter(Trace.TRACE_TAG_INPUT, "meq", mMotionEventQueueCountDebug); + } + } + + private void sendMotionEventWithOffsetLocked(MotionEvent event, int policyFlags, + long downTime, long offset) { + final int pointerCount = event.getPointerCount(); + PointerCoords[] coords = getTempPointerCoordsWithMinSizeLocked(pointerCount); + PointerProperties[] properties = getTempPointerPropertiesWithMinSizeLocked(pointerCount); + for (int i = 0; i < pointerCount; i++) { + event.getPointerCoords(i, coords[i]); + event.getPointerProperties(i, properties[i]); + } + final long eventTime = event.getEventTime() + offset; + sendInputEvent(MotionEvent.obtain(downTime, eventTime, event.getAction(), pointerCount, + properties, coords, event.getMetaState(), event.getButtonState(), 1.0f, 1.0f, + event.getDeviceId(), event.getEdgeFlags(), event.getSource(), event.getFlags()), + policyFlags); + } + + private PointerCoords[] getTempPointerCoordsWithMinSizeLocked(int size) { + final int oldSize = mTempPointerCoords.length; + if (oldSize < size) { + PointerCoords[] oldTempPointerCoords = mTempPointerCoords; + mTempPointerCoords = new PointerCoords[size]; + System.arraycopy(oldTempPointerCoords, 0, mTempPointerCoords, 0, oldSize); + } + for (int i = oldSize; i < size; i++) { + mTempPointerCoords[i] = new PointerCoords(); + } + return mTempPointerCoords; + } + + private PointerProperties[] getTempPointerPropertiesWithMinSizeLocked(int size) { + final int oldSize = mTempPointerProperties.length; + if (oldSize < size) { + PointerProperties[] oldTempPointerProperties = mTempPointerProperties; + mTempPointerProperties = new PointerProperties[size]; + System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, oldSize); + } + for (int i = oldSize; i < size; i++) { + mTempPointerProperties[i] = new PointerProperties(); + } + return mTempPointerProperties; + } + + private void sendSynthesizedMotionEventLocked(MotionEvent event, int policyFlags) { + if (event.getPointerCount() == 1) { + event.getPointerCoords(0, mTempPointerCoords[0]); + event.getPointerProperties(0, mTempPointerProperties[0]); + MotionEvent down = MotionEvent.obtain(event.getEventTime(), event.getEventTime(), + MotionEvent.ACTION_DOWN, 1, mTempPointerProperties, mTempPointerCoords, + event.getMetaState(), event.getButtonState(), + 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(), + event.getSource(), event.getFlags()); + Slog.d(TAG, "Synthesized event:" + down); + sendInputEvent(down, policyFlags); + mSyntheticDownTime = event.getEventTime(); + } else { + Slog.w(TAG, "Could not synthesize MotionEvent, this will drop all following events!"); + } + } + + // should never be called + public IBinder asBinder() { + throw new UnsupportedOperationException(); + } + + // called by the input dispatcher thread + public void install(IInputFilterHost host) throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "EdgeGesture input filter installed."); + } + mHost = host; + synchronized (mLock) { + clearAndResetStateLocked(true, false); + } + } + + // called by the input dispatcher thread + public void uninstall() throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "EdgeGesture input filter uninstalled."); + } + } + + // called by a Binder thread + public void dump(PrintWriter pw, String prefix) { + synchronized (mLock) { + pw.print(prefix); + pw.println("mState=" + mState.name()); + pw.print(prefix); + pw.println("mPositions=0x" + Integer.toHexString(mPositions)); + pw.print(prefix); + pw.println("mQueue=" + mMotionEventQueueCountDebug + " items"); + } + } +} diff --git a/services/java/com/android/server/gesture/EdgeGestureService.java b/services/java/com/android/server/gesture/EdgeGestureService.java new file mode 100644 index 0000000000000..9dc9ac859260c --- /dev/null +++ b/services/java/com/android/server/gesture/EdgeGestureService.java @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project (Jens Doll) + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.server.gesture; + +import static com.android.internal.util.gesture.EdgeServiceConstants.POSITION_MASK; +import static com.android.internal.util.gesture.EdgeServiceConstants.SENSITIVITY_DEFAULT; +import static com.android.internal.util.gesture.EdgeServiceConstants.SENSITIVITY_MASK; +import static com.android.internal.util.gesture.EdgeServiceConstants.SENSITIVITY_NONE; +import static com.android.internal.util.gesture.EdgeServiceConstants.SENSITIVITY_SHIFT; +import static com.android.internal.util.gesture.EdgeServiceConstants.LONG_LIVING; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Parcel; +import android.os.RemoteException; +import android.service.gesture.IEdgeGestureActivationListener; +import android.service.gesture.IEdgeGestureHostCallback; +import android.service.gesture.IEdgeGestureService; +import android.util.Slog; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.WindowManager; + +import com.android.internal.util.gesture.EdgeGesturePosition; +import com.android.server.gesture.EdgeGestureInputFilter; +import com.android.server.input.InputManagerService; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * A system service to track and handle edge swipe gestures. This service interacts with + * the {@link InputManagerService} to do all the dirty work for listeners: + *

  • Installing an input filter and listen for edge swipe gestures
  • + *
  • Removing those gestures from the input stream
  • + *
  • Transferring touch focus to new recipient
  • + */ +public class EdgeGestureService extends IEdgeGestureService.Stub { + public static final String TAG = "EdgeGestureService"; + public static final boolean DEBUG = false; + public static final boolean DEBUG_INPUT = false; + + public static final int MSG_EDGE_GESTURE_ACTIVATION = 32023; + public static final int MSG_UPDATE_SERVICE = 32025; + + private final Context mContext; + private final InputManagerService mInputManager; + + private final HandlerThread mHandlerThread = new HandlerThread("EdgeGestureHandler"); + private Handler mHandler; + + // Lock for mInputFilter, activations and listener related variables + private final Object mLock = new Object(); + private EdgeGestureInputFilter mInputFilter; + + private int mGlobalPositions = 0; + private int mGlobalSensitivity = 3; + + private final class EdgeGestureActivationListenerRecord extends IEdgeGestureHostCallback.Stub implements DeathRecipient { + private boolean mActive; + + public EdgeGestureActivationListenerRecord(IEdgeGestureActivationListener listener) { + this.listener = listener; + this.positions = 0; + } + + public void binderDied() { + removeListenerRecord(this); + } + + private void updateFlags(int flags) { + this.positions = flags & POSITION_MASK; + this.sensitivity = (flags & SENSITIVITY_MASK) >> SENSITIVITY_SHIFT; + this.longLiving = (flags & LONG_LIVING) != 0; + } + + private boolean eligibleForActivation(int positionFlag) { + return (positions & positionFlag) != 0; + } + + private boolean notifyEdgeGestureActivation(int touchX, int touchY, EdgeGesturePosition position) { + if ((positions & position.FLAG) != 0) { + try { + mActive = true; + listener.onEdgeGestureActivation(touchX, touchY, position.INDEX, 0); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify process, assuming it died.", e); + mActive = false; + binderDied(); + } + } + return mActive; + } + + // called through Binder + public boolean gainTouchFocus(IBinder windowToken) { + if (DEBUG) { + Slog.d(TAG, "Gain touch focus for " + windowToken); + } + if (mActive) { + return mInputFilter.unlockFilter(); + } + return false; + } + + public boolean dropEventsUntilLift() { + if (DEBUG) { + Slog.d(TAG, "Will drop all next events till touch up"); + } + if (mActive) { + return mInputFilter.dropSequence(); + } + return false; + } + + // called through Binder + public void restoreListenerState() throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "Restore listener state"); + } + if (mActive) { + mInputFilter.unlockFilter(); + mActive = false; + synchronized (mLock) { + // restore input filter state by updating + mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE); + } + } + } + + public boolean isActive() { + return mActive; + } + + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); + pw.print("mPositions=0x" + Integer.toHexString(positions)); + pw.println(" mActive=" + mActive); + pw.print(prefix); + pw.println("mBinder=" + listener); + } + + public int positions; + public int sensitivity; + public final IEdgeGestureActivationListener listener; + public boolean longLiving = false; + } + private final List mEdgeGestureActivationListener = + new ArrayList(); + // end of lock guarded variables + + private DisplayObserver mDisplayObserver; + + // called by system server + public EdgeGestureService(Context context, InputManagerService inputManager) { + mContext = context; + mInputManager = inputManager; + } + + // called by system server + public void systemReady() { + if (DEBUG) Slog.d(TAG, "Starting the edge gesture capture thread ..."); + + mHandlerThread.start(); + mHandler = new H(mHandlerThread.getLooper()); + mHandler.post(new Runnable() { + @Override + public void run() { + android.os.Process.setThreadPriority( + android.os.Process.THREAD_PRIORITY_FOREGROUND); + android.os.Process.setCanSelfBackground(false); + } + }); + mDisplayObserver = new DisplayObserver(mContext, mHandler); + // check if anyone registered during startup + mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE); + } + + + private void updateMonitoring() { + synchronized(mLock) { + mGlobalPositions = 0; + mGlobalSensitivity = SENSITIVITY_NONE; + boolean someLongLiving = false; + int activePositions = 0; + for (EdgeGestureActivationListenerRecord temp : mEdgeGestureActivationListener) { + mGlobalPositions |= temp.positions; + if (temp.isActive()) { + activePositions |= temp.positions; + } + if (temp.sensitivity != SENSITIVITY_NONE) { + mGlobalSensitivity = Math.max(mGlobalSensitivity, temp.sensitivity); + } + someLongLiving |= temp.longLiving; + } + boolean havePositions = mGlobalPositions != 0; + mGlobalPositions &= ~activePositions; + // if no special sensitivity is requested, we settle on DEFAULT + if (mGlobalSensitivity == SENSITIVITY_NONE) { + mGlobalSensitivity = SENSITIVITY_DEFAULT; + } + + if (mInputFilter == null && havePositions) { + enforceMonitoringLocked(); + } else if (mInputFilter != null && !havePositions && !someLongLiving) { + shutdownMonitoringLocked(); + } + } + } + + private void enforceMonitoringLocked() { + if (DEBUG) { + Slog.d(TAG, "Attempting to start monitoring input events ..."); + } + mInputFilter = new EdgeGestureInputFilter(mContext, mHandler); + mInputManager.setInputFilter(mInputFilter); + mDisplayObserver.observe(); + } + + private void shutdownMonitoringLocked() { + if (DEBUG) { + Slog.d(TAG, "Shutting down monitoring input events ..."); + } + mDisplayObserver.unobserve(); + mInputManager.setInputFilter(mInputFilter); + mInputFilter = null; + } + + // called through Binder + public IEdgeGestureHostCallback registerEdgeGestureActivationListener(IEdgeGestureActivationListener listener) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.INJECT_EVENTS) + != PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "Permission Denial: can't register from from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); + return null; + } + + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + + EdgeGestureActivationListenerRecord record = null; + synchronized(mLock) { + record = findListenerRecordLocked(listener.asBinder()); + if (record == null) { + record = new EdgeGestureActivationListenerRecord(listener); + try { + listener.asBinder().linkToDeath(record, 0); + } catch (RemoteException e) { + Slog.w(TAG, "Recipient died during registration pid=" + Binder.getCallingPid()); + return null; + } + mEdgeGestureActivationListener.add(record); + } + } + return record; + } + + // called through Binder + public void updateEdgeGestureActivationListener(IBinder listener, int positionFlags) { + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + synchronized(mLock) { + EdgeGestureActivationListenerRecord record = findListenerRecordLocked(listener); + if (record == null) { + Slog.w(TAG, "Unknown listener on update listener. Register first?"); + throw new IllegalStateException("listener not registered"); + } + record.updateFlags(positionFlags); + // update input filter only when #systemReady() was called + if (mHandler != null) { + mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE); + } + } + } + + private EdgeGestureActivationListenerRecord findListenerRecordLocked(IBinder listener) { + for (EdgeGestureActivationListenerRecord record : mEdgeGestureActivationListener) { + if (record.listener.asBinder().equals(listener)) { + return record; + } + } + return null; + } + + private void removeListenerRecord(EdgeGestureActivationListenerRecord record) { + synchronized(mLock) { + mEdgeGestureActivationListener.remove(record); + // restore input filter state by updating + mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE); + } + } + + // called by handler thread + private boolean propagateActivation(int touchX, int touchY, EdgeGesturePosition position) { + synchronized(mLock) { + EdgeGestureActivationListenerRecord target = null; + for (EdgeGestureActivationListenerRecord record : mEdgeGestureActivationListener) { + if (record.eligibleForActivation(position.FLAG)) { + target = record; + break; + } + } + // NOTE: We can do this here because the #onGestureActivation() is a oneway + // Binder call. This means we do not block with holding the mListenerLock!!! + // If this ever change, this needs to be adjusted and if you don't know what + // this means, you should probably not mess around with this code, anyway. + if (target != null && !target.notifyEdgeGestureActivation(touchX, touchY, position)) { + target = null; + } + return target != null; + } + } + + private final class H extends Handler { + public H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message m) { + switch (m.what) { + case MSG_EDGE_GESTURE_ACTIVATION: + if (DEBUG) { + Slog.d(TAG, "Activating edge gesture on " + m.obj.toString()); + } + // Since input filter runs asynchronously to us, double activation may happen + // theoretically. Take the safe route here. + removeMessages(MSG_EDGE_GESTURE_ACTIVATION); + if (propagateActivation(m.arg1, m.arg2, (EdgeGesturePosition) m.obj)) { + // switch off activated positions + updateMonitoring(); + updateServiceHandler(mGlobalPositions, mGlobalSensitivity); + } + break; + case MSG_UPDATE_SERVICE: + updateMonitoring(); + if (DEBUG) { + Slog.d(TAG, "Updating positions 0x" + Integer.toHexString(mGlobalPositions) + + " sensitivity: " + mGlobalSensitivity); + } + updateServiceHandler(mGlobalPositions, mGlobalSensitivity); + break; + } + } + + private void updateServiceHandler(int positions, int sensitivity) { + synchronized (mLock) { + if (mInputFilter != null) { + mInputFilter.updatePositions(positions); + mInputFilter.updateSensitivity(sensitivity); + } + } + } + } + + private final class DisplayObserver implements DisplayListener { + private final Handler mHandler; + private final DisplayManager mDisplayManager; + + private final Display mDefaultDisplay; + private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo(); + + public DisplayObserver(Context context, Handler handler) { + mHandler = handler; + mDisplayManager = (DisplayManager) context.getSystemService( + Context.DISPLAY_SERVICE); + final WindowManager windowManager = (WindowManager) context.getSystemService( + Context.WINDOW_SERVICE); + + mDefaultDisplay = windowManager.getDefaultDisplay(); + updateDisplayInfo(); + } + + private void updateDisplayInfo() { + if (DEBUG) { + Slog.d(TAG, "Updating display information ..."); + } + if (mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) { + synchronized (mLock) { + if (mInputFilter != null) { + mInputFilter.updateDisplay(mDefaultDisplay, mDefaultDisplayInfo); + } + } + } else { + Slog.e(TAG, "Default display is not valid."); + } + } + + public void observe() { + mDisplayManager.registerDisplayListener(this, mHandler); + updateDisplayInfo(); + } + + public void unobserve() { + mDisplayManager.unregisterDisplayListener(this); + } + + @Override + public void onDisplayAdded(int displayId) { + /* do noting */ + } + + @Override + public void onDisplayRemoved(int displayId) { + /* do nothing */ + } + + @Override + public void onDisplayChanged(int displayId) { + updateDisplayInfo(); + } + } + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + try { + return super.onTransact(code, data, reply, flags); + } catch (RuntimeException e) { + // let's log all exceptions we do not know about. + if (!(e instanceof IllegalArgumentException || e instanceof IllegalStateException)) { + Slog.e(TAG, "EdgeGestureService crashed: ", e); + } + throw e; + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump EdgeGestureService from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + pw.println("EDGE GESTURE SERVICE (dumpsys edgegestureservice)\n"); + synchronized(mLock) { + pw.println(" mInputFilter=" + mInputFilter); + if (mInputFilter != null) { + mInputFilter.dump(pw, " "); + } + pw.println(" mGlobalPositions=0x" + Integer.toHexString(mGlobalPositions)); + pw.println(" mGlobalSensitivity=" + mGlobalSensitivity); + int i = 0; + for (EdgeGestureActivationListenerRecord record : mEdgeGestureActivationListener) { + if (record.isActive()) { + pw.println(" Active record: #" + (i + 1)); + } + } + i = 0; + for (EdgeGestureActivationListenerRecord record : mEdgeGestureActivationListener) { + pw.println(" Listener #" + i + ":"); + record.dump(pw, " "); + i++; + } + } + } +} diff --git a/services/java/com/android/server/gesture/EdgeGestureTracker.java b/services/java/com/android/server/gesture/EdgeGestureTracker.java new file mode 100644 index 0000000000000..17cd95eda0d0b --- /dev/null +++ b/services/java/com/android/server/gesture/EdgeGestureTracker.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project (Jens Doll) + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.server.gesture; + +import android.graphics.Point; +import android.os.SystemClock; +import android.util.Slog; +import android.view.Display; +import android.view.MotionEvent; + +import com.android.internal.util.gesture.EdgeGesturePosition; +import com.android.internal.util.gesture.EdgeServiceConstants; + +/** + * A simple {@link MotionEvent} tracker class. The main aim of this tracker is to + * reject gestures as fast as possible, so there is only a small amount of events + * that will be delayed. + */ +public class EdgeGestureTracker { + public final static String TAG = "EdgeGestureTracker"; + public final static boolean DEBUG = false; + + public final static long TRIGGER_TIME_MS = 140; + public final static int PIXEL_SWIPE_OFFTAKE_SLOP = 2; + + private final int mBaseThickness; + private final int mBaseTriggerDistance; + private final int mBasePerpendicularDistance; + + private int mThickness; + private int mTriggerDistance; + private int mPerpendicularDistance; + private int mGracePeriodDistance; + private long mTimeOut; + + private int mDisplayWidth; + private int mDisplayHeight; + + private boolean mActive; + private EdgeGesturePosition mPosition; + private long mDownTime; + private int mInitialX; + private int mInitialY; + private int mOffTake; + private int mGracePeriod; + + public interface OnActivationListener { + public void onActivation(MotionEvent event, int touchX, int touchY, EdgeGesturePosition position); + } + private OnActivationListener mActivationListener; + + public EdgeGestureTracker(int thickness, int distance, int perpendicular) { + if (DEBUG) { + Slog.d(TAG, "init: " + thickness + "," + distance); + } + mBaseThickness = thickness; + mBaseTriggerDistance = distance; + mBasePerpendicularDistance = perpendicular; + setSensitivity(0); + } + + private void setSensitivity(int sensitivity) { + float factor = 0.0f; + if (sensitivity >= 1) { + factor = (sensitivity - 1) / 4.0f; + } + if (DEBUG) { + Slog.d(TAG, "sensitivity: " + sensitivity + " => factor:" + factor); + } + // default values (without overlay): + // 140ms ... 210ms + mTimeOut = (long) (TRIGGER_TIME_MS * (factor + 1.0f)); + // 12dp ... 18dp + mThickness = (int) (mBaseThickness * (factor + 1.0f)); + // 12dp ... 6dp + mTriggerDistance = (int) (mBaseTriggerDistance * (1.0f - factor)); + mPerpendicularDistance = (int) (mBasePerpendicularDistance * (1.0f - factor)); + mGracePeriodDistance = (int) (mThickness / 3.0f); + } + + public void setOnActivationListener(OnActivationListener listener) { + mActivationListener = listener; + } + + public void reset() { + mActive = false; + } + + public void updateDisplay(Display display) { + Point outSize = new Point(0,0); + display.getRealSize(outSize); + mDisplayWidth = outSize.x; + mDisplayHeight = outSize.y; + if (DEBUG) { + Slog.d(TAG, "new display: " + mDisplayWidth + "," + mDisplayHeight); + } + } + + public boolean start(MotionEvent motionEvent, int positions, int sensitivity) { + final boolean unrestricted = (positions & EdgeServiceConstants.UNRESTRICTED) != 0; + final int x = (int) motionEvent.getX(); + final float fx = motionEvent.getX() / mDisplayWidth; + final int y = (int) motionEvent.getY(); + final float fy = motionEvent.getY() / mDisplayHeight; + + // calculate trigger geometry based on sensitivity + setSensitivity(sensitivity); + + if ((positions & EdgeGesturePosition.LEFT.FLAG) != 0) { + if (x < mThickness && (unrestricted || (fy > 0.1f && fy < 0.9f))) { + startWithPosition(motionEvent, EdgeGesturePosition.LEFT); + return true; + } + } + if ((positions & EdgeGesturePosition.BOTTOM.FLAG) != 0) { + if (y > mDisplayHeight - mThickness && (unrestricted || (fx > 0.1f && fx < 0.9f))) { + startWithPosition(motionEvent, EdgeGesturePosition.BOTTOM); + return true; + } + } + if ((positions & EdgeGesturePosition.RIGHT.FLAG) != 0) { + if (x > mDisplayWidth - mThickness && (unrestricted || (fy > 0.1f && fy < 0.9f))) { + startWithPosition(motionEvent, EdgeGesturePosition.RIGHT); + return true; + } + } + if ((positions & EdgeGesturePosition.TOP.FLAG) != 0) { + if (y < mThickness && (unrestricted || (fx > 0.1f && fx < 0.9f))) { + startWithPosition(motionEvent, EdgeGesturePosition.TOP); + return true; + } + } + return false; + } + + private void startWithPosition(MotionEvent motionEvent, EdgeGesturePosition position) { + if (DEBUG) { + Slog.d(TAG, "start tracking from " + position.name()); + } + + mDownTime = motionEvent.getDownTime(); + this.mPosition = position; + mInitialX = (int) motionEvent.getX(); + mInitialY = (int) motionEvent.getY(); + switch (position) { + case LEFT: + mGracePeriod = mGracePeriodDistance; + mOffTake = mInitialX - PIXEL_SWIPE_OFFTAKE_SLOP; + break; + case BOTTOM: + mOffTake = mInitialY + PIXEL_SWIPE_OFFTAKE_SLOP; + break; + case RIGHT: + mGracePeriod = mDisplayWidth - mGracePeriodDistance; + mOffTake = mInitialX + PIXEL_SWIPE_OFFTAKE_SLOP; + break; + case TOP: + mOffTake = mInitialY - PIXEL_SWIPE_OFFTAKE_SLOP; + break; + } + mActive = true; + } + + public boolean move(MotionEvent motionEvent) { + if (!mActive || motionEvent.getEventTime() - mDownTime > mTimeOut) { + Slog.d(TAG, "edge gesture timeout: " + (motionEvent.getEventTime() - mDownTime)); + mActive = false; + return false; + } + + final int x = (int) motionEvent.getX(); + final int y = (int) motionEvent.getY(); + final int deltaX = x - mInitialX; + final int deltaY = y - mInitialY; + + if (DEBUG) { + Slog.d(TAG, "move at " + x + "," + y + " " + deltaX + "," + deltaY); + } + + boolean loaded = false; + switch (mPosition) { + case LEFT: + if (x < mGracePeriod) { + mInitialY = y; + } + if (deltaY < mPerpendicularDistance && deltaY > -mPerpendicularDistance + && x >= mOffTake) { + if (deltaX < mTriggerDistance) { + mOffTake = x - PIXEL_SWIPE_OFFTAKE_SLOP; + return true; + } + loaded = true; + } + break; + case BOTTOM: + if (deltaX < mPerpendicularDistance && deltaX > -mPerpendicularDistance + && y <= mOffTake) { + if (deltaY > -mTriggerDistance) { + mOffTake = y + PIXEL_SWIPE_OFFTAKE_SLOP; + return true; + } + loaded = true; + } + break; + case RIGHT: + if (x > mGracePeriod) { + mInitialY = y; + } + if (deltaY < mPerpendicularDistance && deltaY > -mPerpendicularDistance + && x <= mOffTake) { + if (deltaX > -mTriggerDistance) { + mOffTake = x + PIXEL_SWIPE_OFFTAKE_SLOP; + return true; + } + loaded = true; + } + break; + case TOP: + if (deltaX < mPerpendicularDistance && deltaX > -mPerpendicularDistance + && y >= mOffTake) { + if (deltaY < mTriggerDistance) { + mOffTake = y - PIXEL_SWIPE_OFFTAKE_SLOP; + return true; + } + loaded = true; + } + break; + } + mActive = false; + if (loaded && mActivationListener != null) { + if (DEBUG) { + Slog.d(TAG, "activate at " + x + "," + y + " " + mPosition + " within " + + (SystemClock.uptimeMillis() - mDownTime) + "ms"); + } + mActivationListener.onActivation(motionEvent, x, y, mPosition); + } + return loaded; + } +} \ No newline at end of file From fa8cb9bb8707690f59a09da352a7360349ae9241 Mon Sep 17 00:00:00 2001 From: aoleary Date: Mon, 24 Sep 2018 22:14:46 +0200 Subject: [PATCH 66/73] Revert "Add edge gestures (1/2)" This reverts commit 6cf16e78a48f11a136f1c163b2f040d4f7ebbf69. --- Android.mk | 3 - core/java/android/provider/Settings.java | 43 -- .../service/gesture/EdgeGestureManager.java | 213 ------- .../IEdgeGestureActivationListener.aidl | 14 - .../gesture/IEdgeGestureHostCallback.aidl | 20 - .../service/gesture/IEdgeGestureService.aidl | 20 - .../java/android/service/gesture/package.html | 5 - core/java/android/view/InputDevice.java | 9 - .../util/gesture/EdgeGesturePosition.java | 42 -- .../util/gesture/EdgeServiceConstants.java | 82 --- core/res/res/values/aos_config.xml | 5 - core/res/res/values/aos_dimens.xml | 12 - .../systemui/statusbar/phone/StatusBar.java | 32 -- .../ScreenGesturesController.java | 176 ------ .../screen_gestures/ScreenGesturesView.java | 222 -------- .../server/policy/PhoneWindowManager.java | 90 --- .../java/com/android/server/SystemServer.java | 16 +- .../gesture/EdgeGestureInputFilter.java | 539 ------------------ .../server/gesture/EdgeGestureService.java | 486 ---------------- .../server/gesture/EdgeGestureTracker.java | 252 -------- 20 files changed, 3 insertions(+), 2278 deletions(-) delete mode 100644 core/java/android/service/gesture/EdgeGestureManager.java delete mode 100644 core/java/android/service/gesture/IEdgeGestureActivationListener.aidl delete mode 100644 core/java/android/service/gesture/IEdgeGestureHostCallback.aidl delete mode 100644 core/java/android/service/gesture/IEdgeGestureService.aidl delete mode 100644 core/java/android/service/gesture/package.html delete mode 100644 core/java/com/android/internal/util/gesture/EdgeGesturePosition.java delete mode 100644 core/java/com/android/internal/util/gesture/EdgeServiceConstants.java delete mode 100644 core/res/res/values/aos_dimens.xml delete mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesController.java delete mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesView.java delete mode 100644 services/java/com/android/server/gesture/EdgeGestureInputFilter.java delete mode 100644 services/java/com/android/server/gesture/EdgeGestureService.java delete mode 100644 services/java/com/android/server/gesture/EdgeGestureTracker.java diff --git a/Android.mk b/Android.mk index 2a26498d30cb0..cf82b96f6419e 100644 --- a/Android.mk +++ b/Android.mk @@ -342,9 +342,6 @@ LOCAL_SRC_FILES += \ core/java/android/service/voice/IVoiceInteractionSession.aidl \ core/java/android/service/voice/IVoiceInteractionSessionService.aidl \ core/java/android/service/gesture/IGestureService.aidl \ - core/java/android/service/gesture/IEdgeGestureService.aidl \ - core/java/android/service/gesture/IEdgeGestureActivationListener.aidl \ - core/java/android/service/gesture/IEdgeGestureHostCallback.aidl \ core/java/android/service/wallpaper/IWallpaperConnection.aidl \ core/java/android/service/wallpaper/IWallpaperEngine.aidl \ core/java/android/service/wallpaper/IWallpaperService.aidl \ diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ad72e9b961668..e250c5e206866 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4834,13 +4834,6 @@ public boolean validate(String value) { */ public static final String CHOOSER_ACTIVITY_BLACKLIST = "chooser_activity_blacklist"; - /** - * Use EdgeGesture Service for system gestures in PhoneWindowManager - * @hide - */ - public static final String USE_EDGE_SERVICE_FOR_GESTURES = "edge_service_for_gestures"; - - /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. @@ -8463,42 +8456,6 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val */ public static final String STATUS_BAR_BATTERY_STYLE = "status_bar_battery_style"; - /** - * Whether to use edge gestures to navigate. - * @hide - */ - public static final String EDGE_GESTURES_ENABLED = "edge_gestures_enabled"; - - /** - * Haptic feedback duration on edge gesture navigation. - * @hide - */ - public static final String EDGE_GESTURES_FEEDBACK_DURATION = "edge_gestures_feedback_duration"; - - /** - * Long press duration on edge gesture navigation. - * @hide - */ - public static final String EDGE_GESTURES_LONG_PRESS_DURATION = "edge_gestures_long_press_duration"; - - /** - * Back gesture active on this edges. - * @hide - */ - public static final String EDGE_GESTURES_BACK_EDGES = "edge_gestures_back_edges"; - - /** - * Back gesture active on this edges when on landscape. - * @hide - */ - public static final String EDGE_GESTURES_LANDSCAPE_BACK_EDGES = "edge_gestures_landscape_back_edges"; - - /** - * Activate back gestures only when Y position > than this % of screen. - * @hide - */ - public static final String EDGE_GESTURES_BACK_SCREEN_PERCENT = "edge_gestures_back_screen_percent"; - /** * This are the settings to be backed up. * diff --git a/core/java/android/service/gesture/EdgeGestureManager.java b/core/java/android/service/gesture/EdgeGestureManager.java deleted file mode 100644 index 18818443f9374..0000000000000 --- a/core/java/android/service/gesture/EdgeGestureManager.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2013 The CyanogenMod Project (Jens Doll) - * - * 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.service.gesture; - -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.service.gesture.IEdgeGestureActivationListener; -import android.service.gesture.IEdgeGestureHostCallback; -import android.service.gesture.IEdgeGestureService; -import android.util.Slog; - -import com.android.internal.util.gesture.EdgeGesturePosition; - -/** - * This is a simple Manager class for edge gesture service on the application side. The application need - * {@code INJECT_EVENTS} permission to register {@code EdgeGestureActivationListener}s.
    - * See {@link android.service.gesture.IEdgeGestureService} for more information. - * - * @see android.service.gesture.IEdgeGestureService - * @hide - */ -public class EdgeGestureManager { - public static final String TAG = "EdgeGestureManager"; - public static final boolean DEBUG = false; - - private static EdgeGestureManager sInstance; - - private final IEdgeGestureService mPs; - - public static abstract class EdgeGestureActivationListener { - private Handler mHandler; - private IEdgeGestureHostCallback mCallback; - - private class Delegator extends IEdgeGestureActivationListener.Stub { - public void onEdgeGestureActivation(final int touchX, final int touchY, final int positionIndex, final int flags) - throws RemoteException { - mHandler.post(new Runnable() { - public void run() { - EdgeGestureActivationListener.this.onEdgeGestureActivation(touchX, touchY, EdgeGesturePosition.values()[positionIndex], flags); - } - }); - } - } - private Delegator mDelegator; - - public EdgeGestureActivationListener() { - this(Looper.getMainLooper()); - } - - public EdgeGestureActivationListener(Looper looper) { - mHandler = new Handler(looper); - mDelegator = new Delegator(); - } - - /* package */ void setHostCallback(IEdgeGestureHostCallback hostCallback) { - mCallback = hostCallback; - } - - /** - * Override this to receive activations from the edge gesture service. - * - * @param touchX the last X position a touch event was registered. - * @param touchY the last Y position a touch event was registered. - * @param position the position of the activation. - * @param flags currently 0. - * @see IEdgeGestureActivationListener#onEdgeGestureActivation(int, int, int, int) - */ - public abstract void onEdgeGestureActivation(int touchX, int touchY, EdgeGesturePosition position, int flags); - - /** - * After being activated, this allows the edge gesture control to steal focus from the current - * window. - * - * @see IEdgeGestureHostCallback#gainTouchFocus(IBinder) - */ - public boolean gainTouchFocus(IBinder applicationWindowToken) { - try { - return mCallback.gainTouchFocus(applicationWindowToken); - } catch (RemoteException e) { - Slog.w(TAG, "gainTouchFocus failed: " + e.getMessage()); - /* fall through */ - } - return false; - } - - public boolean dropEventsUntilLift() { - try { - return mCallback.dropEventsUntilLift(); - } catch (RemoteException e) { - Slog.w(TAG, "dropNextEvents failed: " + e.getMessage()); - /* fall through */ - } - return false; - } - - /** - * Turns listening for edge gesture activation gestures on again, after it was disabled during - * the call to the listener. - * - * @see IEdgeGestureHostCallback#restoreListenerState() - */ - public void restoreListenerState() { - if (DEBUG) { - Slog.d(TAG, "restore listener state: " + Thread.currentThread().getName()); - } - try { - mCallback.restoreListenerState(); - } catch (RemoteException e) { - Slog.w(TAG, "restoreListenerState failed: " + e.getMessage()); - /* fall through */ - } - } - } - - private EdgeGestureManager(IEdgeGestureService ps) { - mPs = ps; - } - - /** - * Gets an instance of the edge gesture manager. - * - * @return The edge gesture manager instance. - * @hide - */ - public static EdgeGestureManager getInstance() { - synchronized (EdgeGestureManager.class) { - if (sInstance == null) { - IBinder b = ServiceManager.getService("edgegestureservice"); - sInstance = new EdgeGestureManager(IEdgeGestureService.Stub.asInterface(b)); - } - return sInstance; - } - } - - /** - * Checks if the edge gesture service is present. - *

    - * Since the service is only started at boot time and is bound to the system server, this - * is constant for the devices up time. - * - * @return {@code true} when the edge gesture service is running on this device. - * @hide - */ - public boolean isPresent() { - return mPs != null; - } - - /** - * Register a listener for edge gesture activation gestures. Initially the listener - * is set to listen for no position. Use updateedge gestureActivationListener() to - * bind the listener to positions. - * - * @param listener is the activation listener. - * @return {@code true} if the registration was successful. - * @hide - */ - public boolean setEdgeGestureActivationListener(EdgeGestureActivationListener listener) { - if (DEBUG) { - Slog.d(TAG, "Set edge gesture activation listener"); - } - if (mPs == null) { - Slog.e(TAG, "Failed to set edge gesture activation listener: Service not present"); - return false; - } - try { - IEdgeGestureHostCallback callback = mPs.registerEdgeGestureActivationListener(listener.mDelegator); - listener.setHostCallback(callback); - return true; - } catch (RemoteException e) { - Slog.e(TAG, "Failed to set edge gesture activation listener: " + e.getMessage()); - return false; - } - } - - /** - * Update the listener to react on gestures in the given positions. - * - * @param listener is a already registered listener. - * @param positions is a bit mask describing the positions to listen to. - * @hide - */ - public void updateEdgeGestureActivationListener(EdgeGestureActivationListener listener, int positions) { - if (DEBUG) { - Slog.d(TAG, "Update edge gesture activation listener: 0x" + Integer.toHexString(positions)); - } - if (mPs == null) { - Slog.e(TAG, "Failed to update edge gesture activation listener: Service not present"); - return; - } - try { - mPs.updateEdgeGestureActivationListener(listener.mDelegator.asBinder(), positions); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to update edge gesture activation listener: " + e.getMessage()); - } - } - -} diff --git a/core/java/android/service/gesture/IEdgeGestureActivationListener.aidl b/core/java/android/service/gesture/IEdgeGestureActivationListener.aidl deleted file mode 100644 index 0c9b24a8ae3d1..0000000000000 --- a/core/java/android/service/gesture/IEdgeGestureActivationListener.aidl +++ /dev/null @@ -1,14 +0,0 @@ - -package android.service.gesture; - -import android.view.InputEvent; - -/** @hide */ -interface IEdgeGestureActivationListener { - - /** Called when a gesture is detected that fits to the activation gesture. At this point in - * time gesture detection is disabled. Call IEdgeGestureHostCallback.restoreState() to - * recover from this. - */ - oneway void onEdgeGestureActivation(int touchX, int touchY, int positionIndex, int flags); -} \ No newline at end of file diff --git a/core/java/android/service/gesture/IEdgeGestureHostCallback.aidl b/core/java/android/service/gesture/IEdgeGestureHostCallback.aidl deleted file mode 100644 index c2615503e4fc3..0000000000000 --- a/core/java/android/service/gesture/IEdgeGestureHostCallback.aidl +++ /dev/null @@ -1,20 +0,0 @@ -package android.service.gesture; - -/** @hide */ -interface IEdgeGestureHostCallback { - - /** After being activated, this allows to steal focus from the current - * window - */ - boolean gainTouchFocus(IBinder windowToken); - - /** Turns listening for activation gestures on again, after it was disabled during - * the call to the listener. - */ - oneway void restoreListenerState(); - - /* - * Tells filter to drop all events till touch up - */ - boolean dropEventsUntilLift(); -} \ No newline at end of file diff --git a/core/java/android/service/gesture/IEdgeGestureService.aidl b/core/java/android/service/gesture/IEdgeGestureService.aidl deleted file mode 100644 index 342cf71aa32bf..0000000000000 --- a/core/java/android/service/gesture/IEdgeGestureService.aidl +++ /dev/null @@ -1,20 +0,0 @@ -package android.service.gesture; - -import android.service.gesture.IEdgeGestureActivationListener; -import android.service.gesture.IEdgeGestureHostCallback; - -/** @hide */ -interface IEdgeGestureService { - - /** Register a listener for activation gestures. Initially the listener - * is set to listen for no position. Use updateEdgeGestureActivationListener() to - * bind the listener to positions. - * Use the returned IEdgeGestureHostCallback to manipulate the state after activation. - */ - IEdgeGestureHostCallback registerEdgeGestureActivationListener(in IEdgeGestureActivationListener listener); - - /** Update the listener to react on gestures in the given positions. - */ - void updateEdgeGestureActivationListener(in IBinder listener, int positionFlags); - -} \ No newline at end of file diff --git a/core/java/android/service/gesture/package.html b/core/java/android/service/gesture/package.html deleted file mode 100644 index c9f96a66ab3bc..0000000000000 --- a/core/java/android/service/gesture/package.html +++ /dev/null @@ -1,5 +0,0 @@ - - -{@hide} - - diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index e3e7dfad49c81..a8b07c98a25c6 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -310,15 +310,6 @@ public final class InputDevice implements Parcelable { */ public static final int SOURCE_NAVIGATION_BAR = 0x04000000 | SOURCE_CLASS_BUTTON; - /** - * The input source is a custom virtual key event sent programmatically to emulate different events. - * - * The key requested is different from the actual key's event. - * - * @hide - */ - public static final int SOURCE_CUSTOM = 0x08000000 | SOURCE_CLASS_BUTTON; - /** * A special input source constant that is used when filtering input devices * to match devices that provide any type of input source. diff --git a/core/java/com/android/internal/util/gesture/EdgeGesturePosition.java b/core/java/com/android/internal/util/gesture/EdgeGesturePosition.java deleted file mode 100644 index 01cfdeae54fbe..0000000000000 --- a/core/java/com/android/internal/util/gesture/EdgeGesturePosition.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2013 The CyanogenMod Project (Jens Doll) - * - * 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.internal.util.gesture; - -/** - * Defines the positions in which gestures may be recognized by the - * edge gesture service. - * This defines an index and an flag for each position. - */ -public enum EdgeGesturePosition { - LEFT(0, 0), - BOTTOM(1, 1), - RIGHT(2, 1), - TOP(3, 0); - - EdgeGesturePosition(int index, int factor) { - INDEX = index; - FLAG = (0x01< - * Positions are specified by {@code EdgeGesturePosition.FLAG}. - */ - public static final int POSITION_MASK = 0x0000001f; - - /** - * Mask for coding sensitivity within the flags of - * {@code updateEdgeGestureActivationListener()}. - *

    - * Sensitivity influences the speed of the swipe, the trigger area, and trigger distance that - * is needed to activate the edge gesture. - */ - public static final int SENSITIVITY_MASK = 0x70000000; - - /** - * Number of bits to shift left, to get a integer within the {@link #SENSITIVITY_MASK}. - */ - public static final int SENSITIVITY_SHIFT = 28; - - /** - * No sensitivity specified at all, the service may choose a sensitivity level on its own. - */ - public static final int SENSITIVITY_NONE = 0; - - /** - * Default sensitivity, picked by the edge gesture service automatically. - */ - public static final int SENSITIVITY_DEFAULT = 2; - - /** - * Lowest valid sensitivity value. - */ - public static final int SENSITIVITY_LOWEST = 1; - - /** - * Highest sensitivity value. - */ - public static final int SENSITIVITY_HIGHEST = 4; - - /** - * Do not cut 10% area on th edges - */ - public static final int UNRESTRICTED = 0x10; - - /** - * This listener does not likes enabling/disabling filter - * because it interrupt in motion events. - */ - public static final int LONG_LIVING = 0x20; - -} diff --git a/core/res/res/values/aos_config.xml b/core/res/res/values/aos_config.xml index cd9c82a74ae8f..c370c3f4bdff9 100644 --- a/core/res/res/values/aos_config.xml +++ b/core/res/res/values/aos_config.xml @@ -138,9 +138,4 @@ false - - - - - diff --git a/core/res/res/values/aos_dimens.xml b/core/res/res/values/aos_dimens.xml deleted file mode 100644 index 3896e338e5301..0000000000000 --- a/core/res/res/values/aos_dimens.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - 12dp - - - 15dp - - - 12dp - diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 45a79809e2083..8d5481228a033 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -271,7 +271,6 @@ import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.statusbar.screen_gestures.ScreenGesturesController; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout .OnChildLocationsChangedListener; @@ -419,9 +418,6 @@ public class StatusBar extends SystemUI implements DemoMode, */ private static final float SRC_MIN_ALPHA = 0.002f; - private static final String EDGE_GESTURES_ENABLED = - Settings.Secure.EDGE_GESTURES_ENABLED; - static { boolean onlyCoreApps; boolean freeformWindowManagement; @@ -545,10 +541,6 @@ public class StatusBar extends SystemUI implements DemoMode, private int mAmbientMediaPlaying; - // Full Screen Gestures - protected ScreenGesturesController gesturesController; - private boolean mEdgeGesturesEnabled; - // Tracking finger for opening/closing. boolean mTracking; @@ -6677,10 +6669,6 @@ void observe() { resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.LOCKSCREEN_DATE_SELECTION), false, this, UserHandle.USER_ALL); - resolver.registerContentObserver(Settings.Secure.getUriFor( - Settings.Secure.EDGE_GESTURES_ENABLED), - false, this, UserHandle.USER_ALL); - update(); } @Override @@ -6751,9 +6739,6 @@ public void onChange(boolean selfChange, Uri uri) { uri.equals(Settings.System.getUriFor(Settings.System.LOCKSCREEN_CLOCK_SELECTION)) || uri.equals(Settings.System.getUriFor(Settings.System.LOCKSCREEN_DATE_SELECTION))) { updateKeyguardStatusSettings(); - } else if (uri.equals(Settings.Secure.getUriFor( - Settings.Secure.EDGE_GESTURES_ENABLED))) { - updateEdgeGestures(); } } @@ -6770,7 +6755,6 @@ public void update() { updateRecentsMode(); updateTickerAnimation(); updateKeyguardStatusSettings(); - updateEdgeGestures(); } } @@ -8732,20 +8716,4 @@ public void startAssist(Bundle args) { mNavigationBar.getBarTransitions().setAutoDim(true); } }; - - public void updateEdgeGestures() { - Log.d(TAG, "updateEdgeGestures: Updating edge gestures"); - boolean enabled = Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.EDGE_GESTURES_ENABLED, 0, mCurrentUserId) == 1; - if (enabled) { - if (gesturesController == null) { - gesturesController = new ScreenGesturesController(mContext, mWindowManager, this); - } - gesturesController.reorient(); - } else if (!enabled && gesturesController != null) { - gesturesController.stop(); - gesturesController = null; - } - } - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesController.java b/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesController.java deleted file mode 100644 index b33fe689bac5e..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesController.java +++ /dev/null @@ -1,176 +0,0 @@ -package com.android.systemui.statusbar.screen_gestures; - -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.PixelFormat; -import android.graphics.Point; -import android.hardware.input.InputManager; -import android.os.Handler; -import android.os.Looper; -import android.os.SystemClock; -import android.os.UserHandle; -import android.os.Vibrator; -import android.provider.Settings; -import android.service.gesture.EdgeGestureManager; -import android.util.Log; -import android.view.Gravity; -import android.view.InputDevice; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; -import android.view.ViewGroup; -import android.view.WindowManager; - -import com.android.internal.util.gesture.EdgeGesturePosition; -import com.android.internal.util.gesture.EdgeServiceConstants; -import com.android.systemui.statusbar.phone.StatusBar; - -/** - * Created by arasthel on 15/02/18. - */ - -public class ScreenGesturesController { - - public static final boolean DEBUG = false; - - private static final String TAG = "ScreenGesturesControlle"; - - private Context context; - private WindowManager windowManager; - private StatusBar statusBar; - - private ScreenGesturesView screenGesturesView; - - private EdgeGestureManager edgeGestureManager = EdgeGestureManager.getInstance(); - - private EdgeGestureManager.EdgeGestureActivationListener gestureManagerListener = new EdgeGestureManager.EdgeGestureActivationListener() { - - @Override - public void onEdgeGestureActivation(int touchX, int touchY, EdgeGesturePosition position, int flags) { - if (DEBUG) Log.d(TAG, "onEdgeGestureActivation: Starting gesture"); - final ScreenGesturesView gesturesView = screenGesturesView; - - if (gesturesView != null) { - boolean startGesture = true; - String backSettingsId = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? - Settings.Secure.EDGE_GESTURES_BACK_EDGES : - Settings.Secure.EDGE_GESTURES_LANDSCAPE_BACK_EDGES; - int backGestureEdgesFlag = 0; - int percent = 0; - try { - backGestureEdgesFlag = Settings.Secure.getIntForUser(context.getContentResolver(), - backSettingsId, - UserHandle.USER_CURRENT); - percent = Settings.Secure.getIntForUser(context.getContentResolver(), - Settings.Secure.EDGE_GESTURES_BACK_SCREEN_PERCENT, - UserHandle.USER_CURRENT); - } catch (Settings.SettingNotFoundException e) { - e.printStackTrace(); - } - if ((position.FLAG & backGestureEdgesFlag) != 0) { - Point displaySize = new Point(); - windowManager.getDefaultDisplay().getSize(displaySize); - startGesture = touchY > (percent*displaySize.y)/100; - } - - if (startGesture) { - gesturesView.startGesture(touchX, touchY, position); - - handler.post(() -> { - gainTouchFocus(gesturesView.getWindowToken()); - gestureManagerListener.restoreListenerState(); - }); - } else { - gestureManagerListener.restoreListenerState(); - } - } else { - gestureManagerListener.restoreListenerState(); - } - - } - }; - - private ScreenGesturesView.OnGestureCompletedListener onGestureCompletedListener = gestureType -> { - switch (gestureType) { - case ScreenGesturesView.GestureType.HOME: - injectKeyCode(KeyEvent.KEYCODE_HOME); - break; - case ScreenGesturesView.GestureType.BACK: - injectKeyCode(KeyEvent.KEYCODE_BACK); - break; - case ScreenGesturesView.GestureType.RECENTS: - injectKeyCode(KeyEvent.KEYCODE_APP_SWITCH); - break; - default: - Log.e(TAG, "Unknown event"); - break; - } - - gestureManagerListener.restoreListenerState(); - - //setupEdgeGestureManager(); - }; - - private Handler handler = new Handler(Looper.getMainLooper()); - - public ScreenGesturesController(Context context, WindowManager windowManager, StatusBar statusBar) { - this.context = context; - this.windowManager = windowManager; - this.statusBar = statusBar; - - edgeGestureManager.setEdgeGestureActivationListener(gestureManagerListener); - setupEdgeGestureManager(); - - screenGesturesView = new ScreenGesturesView(context); - WindowManager.LayoutParams params = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON - | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, - PixelFormat.TRANSLUCENT); - windowManager.addView(screenGesturesView, params); - - screenGesturesView.setOnGestureCompletedListener(onGestureCompletedListener); - } - - public void reorient() { - setupEdgeGestureManager(); - } - - public void stop() { - gestureManagerListener.restoreListenerState(); - edgeGestureManager.updateEdgeGestureActivationListener(gestureManagerListener, 0); - windowManager.removeView(screenGesturesView); - screenGesturesView = null; - } - - private void setupEdgeGestureManager() { - int sensitivity = EdgeServiceConstants.SENSITIVITY_HIGHEST; - - int edges = (EdgeGesturePosition.BOTTOM.FLAG | EdgeGesturePosition.RIGHT.FLAG | EdgeGesturePosition.LEFT.FLAG); - edgeGestureManager.updateEdgeGestureActivationListener(gestureManagerListener, - sensitivity << EdgeServiceConstants.SENSITIVITY_SHIFT - | edges - | EdgeServiceConstants.LONG_LIVING - | EdgeServiceConstants.UNRESTRICTED); - } - - private void injectKeyCode(int keyCode) { - if (DEBUG) Log.d(TAG, "injectKeyCode: Injecting: " + String.valueOf(keyCode)); - - InputManager inputManager = InputManager.getInstance(); - long now = SystemClock.uptimeMillis(); - - KeyEvent downEvent = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, - KeyEvent.FLAG_FROM_SYSTEM, InputDevice.SOURCE_CUSTOM); - - KeyEvent upEvent = KeyEvent.changeAction(downEvent, KeyEvent.ACTION_UP); - - inputManager.injectInputEvent(downEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); - inputManager.injectInputEvent(upEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); - } -} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesView.java b/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesView.java deleted file mode 100644 index 31981871203d9..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesView.java +++ /dev/null @@ -1,222 +0,0 @@ -package com.android.systemui.statusbar.screen_gestures; - -import android.content.Context; -import android.content.res.Configuration; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; -import android.os.Vibrator; -import android.provider.Settings; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; - -import com.android.internal.util.gesture.EdgeGesturePosition; - -/** - * Created by arasthel on 15/02/18. - */ - -public class ScreenGesturesView extends View { - - public static final boolean DEBUG = false; - - public static final int SIGNIFICANT_MOVE = 10; - - private static final String TAG = "ScreenGesturesView"; - - public static class GestureType { - public static final int NONE = 0; - public static final int HOME = 1; - public static final int BACK = 1 << 1; - public static final int RECENTS = 1 << 2; - } - - private OnGestureCompletedListener onGestureCompletedListener; - - private int initialX = -1; - private int initialY = -1; - - private int lastX = -1; - private int lastY = -1; - - private int possibleGestures = GestureType.NONE; - - private Vibrator vibrator; - - private Handler handler = new Handler(Looper.getMainLooper()); - - public ScreenGesturesView(Context context) { - super(context); - } - - public ScreenGesturesView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public ScreenGesturesView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public ScreenGesturesView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - setVisibility(View.GONE); - - vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); - } - - public void startGesture(int initialX, int initialY, EdgeGesturePosition position) { - if (DEBUG) Log.d(TAG, "startGesture: Gesture started"); - - this.initialX = initialX; - this.initialY = initialY; - - this.lastX = initialX; - this.lastY = initialY; - - String backSettingsId = getContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? - Settings.Secure.EDGE_GESTURES_BACK_EDGES : - Settings.Secure.EDGE_GESTURES_LANDSCAPE_BACK_EDGES; - int backGestureEdgesFlag = Settings.Secure.getIntForUser(getContext().getContentResolver(), - backSettingsId, - 0, - UserHandle.USER_CURRENT);; - - if ((position.FLAG & backGestureEdgesFlag) != 0) { - possibleGestures = GestureType.BACK; - } else if ((position.FLAG & EdgeGesturePosition.BOTTOM.FLAG) != 0) { - possibleGestures = GestureType.HOME | GestureType.RECENTS; - } else { - if (onGestureCompletedListener != null) { - onGestureCompletedListener.onGestureCompleted(GestureType.NONE); - } - return; - } - - setVisibility(View.VISIBLE); - } - - private int getFeedbackStrength() { - try { - return Settings.Secure.getInt(getContext().getContentResolver(), Settings.Secure.EDGE_GESTURES_FEEDBACK_DURATION); - } catch (Settings.SettingNotFoundException exception) { - return 100; - } - } - - private int getLongpressDuration() { - try { - return Settings.Secure.getInt(getContext().getContentResolver(), Settings.Secure.EDGE_GESTURES_LONG_PRESS_DURATION); - } catch (Settings.SettingNotFoundException exception) { - return 500; - } - } - - private void stopGesture(int posX, int posY) { - if (DEBUG) Log.d(TAG, "stopGesture: Gesture stopped"); - - stopLongPress(); - - if (onGestureCompletedListener == null) return; - - if (DEBUG) Log.d(TAG, "stopGesture: Initial x: " + String.valueOf(initialX) + ", final x: " + String.valueOf(posX)); - if (DEBUG) Log.d(TAG, "stopGesture: Initial y: " + String.valueOf(initialY) + ", final y: " + String.valueOf(posY)); - - final int threshold = 20; - boolean canSendHome = (possibleGestures & GestureType.HOME) != 0; - if (canSendHome && (posY - initialY < -threshold)) { - if (DEBUG) Log.d(TAG, "stopGesture: Home"); - vibrator.vibrate(getFeedbackStrength()); - onGestureCompletedListener.onGestureCompleted(GestureType.HOME); - return; - } - - boolean canSendBack = (possibleGestures & GestureType.BACK) != 0; - if (canSendBack && (Math.abs(posX - initialX) > threshold)) { - if (DEBUG) Log.d(TAG, "stopGesture: Back"); - vibrator.vibrate(getFeedbackStrength()); - onGestureCompletedListener.onGestureCompleted(GestureType.BACK); - return; - } - - if (DEBUG) Log.d(TAG, "stopGesture: None"); - onGestureCompletedListener.onGestureCompleted(GestureType.NONE); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - if (DEBUG) Log.d(TAG, "onTouchEvent: DOWN"); - return true; - case MotionEvent.ACTION_MOVE: - if (DEBUG) Log.d(TAG, "onTouchEvent: MOVE"); - int x = (int) event.getX(); - int y = (int) event.getY(); - if (Math.abs(x - lastX) > SIGNIFICANT_MOVE || Math.abs(y - lastY) > SIGNIFICANT_MOVE) { - stopLongPress(); - startLongPress(); - } - - lastX = x; - lastY = y; - return true; - case MotionEvent.ACTION_UP: - if (DEBUG) Log.d(TAG, "onTouchEvent: UP"); - if (possibleGestures != GestureType.NONE) { - stopGesture((int) event.getX(), (int) event.getY()); - setVisibility(View.GONE); - } - return true; - case MotionEvent.ACTION_CANCEL: - if (DEBUG) Log.d(TAG, "onTouchEvent: DOWN"); - stopGesture((int) event.getX(), (int) event.getY()); - setVisibility(View.GONE); - return true; - } - return false; - } - - private void startLongPress() { - if (DEBUG) Log.d(TAG, "startLongPress: scheduling long press"); - handler.postDelayed(longPressRunnable, getLongpressDuration()); - } - - private void stopLongPress() { - if (DEBUG) Log.d(TAG, "stopLongPress: cancellling long press"); - handler.removeCallbacks(longPressRunnable); - } - - private Runnable longPressRunnable = new Runnable() { - @Override - public void run() { - boolean canSendRecents = (possibleGestures & GestureType.RECENTS) != 0; - if (canSendRecents) { - possibleGestures = GestureType.NONE; - setVisibility(View.GONE); - if (onGestureCompletedListener != null) { - onGestureCompletedListener.onGestureCompleted(GestureType.RECENTS); - } - vibrator.vibrate(getFeedbackStrength()); - } - } - }; - - public OnGestureCompletedListener getOnGestureCompletedListener() { - return onGestureCompletedListener; - } - - public void setOnGestureCompletedListener(OnGestureCompletedListener onGestureCompletedListener) { - this.onGestureCompletedListener = onGestureCompletedListener; - } - - interface OnGestureCompletedListener { - void onGestureCompleted(int gestureType); - } -} diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index d6bc46d29e9f3..9ed35690dec15 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -204,7 +204,6 @@ import android.service.dreams.DreamManagerInternal; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; -import android.service.gesture.EdgeGestureManager; import android.service.vr.IPersistentVrStateCallbacks; import android.speech.RecognizerIntent; import android.telecom.TelecomManager; @@ -257,8 +256,6 @@ import com.android.internal.policy.IShortcutService; import com.android.internal.policy.PhoneWindow; import com.android.internal.statusbar.IStatusBarService; -import com.android.internal.util.gesture.EdgeGesturePosition; -import com.android.internal.util.gesture.EdgeServiceConstants; import com.android.internal.util.ScreenShapeHelper; import com.android.internal.utils.du.ActionHandler; import com.android.internal.utils.du.DUActionUtils; @@ -1225,9 +1222,6 @@ void observe() { resolver.registerContentObserver(Settings.Global.getUriFor( Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED), false, this, UserHandle.USER_ALL); - resolver.registerContentObserver(Settings.System.getUriFor( - Settings.System.USE_EDGE_SERVICE_FOR_GESTURES), false, this, - UserHandle.USER_ALL); updateSettings(); } @@ -1322,67 +1316,6 @@ public void onBarVisibilityChanged(boolean visible) { private SystemGesturesPointerEventListener mSystemGestures; - private EdgeGestureManager.EdgeGestureActivationListener mEdgeGestureActivationListener - = new EdgeGestureManager.EdgeGestureActivationListener() { - - @Override - public void onEdgeGestureActivation(int touchX, int touchY, - EdgeGesturePosition position, int flags) { - WindowState target = null; - - if (position == EdgeGesturePosition.TOP) { - target = mStatusBar; - } else if ((position == EdgeGesturePosition.BOTTOM) && (mNavigationBarPosition == NAV_BAR_BOTTOM)) { - target = mNavigationBar; - } else if ((position == EdgeGesturePosition.RIGHT) && (mNavigationBarPosition == NAV_BAR_RIGHT)) { - target = mNavigationBar; - } else if ((position == EdgeGesturePosition.LEFT) && (mNavigationBarPosition == NAV_BAR_LEFT)) { - target = mNavigationBar; - } - - if (target != null) { - requestTransientBars(target); - dropEventsUntilLift(); - mEdgeListenerActivated = true; - } else { - restoreListenerState(); - } - } - }; - private EdgeGestureManager mEdgeGestureManager = null; - private int mLastEdgePositions = 0; - private boolean mEdgeListenerActivated = false; - private boolean mUsingEdgeGestureServiceForGestures = false; - - private void updateEdgeGestureListenerState() { - int flags = 0; - if (mUsingEdgeGestureServiceForGestures) { - flags = EdgeServiceConstants.LONG_LIVING | EdgeServiceConstants.UNRESTRICTED; - if (mStatusBar != null && !mStatusBar.isVisibleLw()) { - flags |= EdgeGesturePosition.TOP.FLAG; - } - if (mNavigationBar != null && !mNavigationBar.isVisibleLw() - && !isStatusBarKeyguard()) { - if (mNavigationBarPosition == NAV_BAR_BOTTOM) { - flags |= EdgeGesturePosition.BOTTOM.FLAG; - } else if (mNavigationBarPosition == NAV_BAR_RIGHT){ - flags |= EdgeGesturePosition.RIGHT.FLAG; - } else if (mNavigationBarPosition == NAV_BAR_LEFT) { - flags |= EdgeGesturePosition.LEFT.FLAG; - } - } - } - if (mEdgeListenerActivated) { - mEdgeGestureActivationListener.restoreListenerState(); - mEdgeListenerActivated = false; - } - if (flags != mLastEdgePositions) { - mEdgeGestureManager.updateEdgeGestureActivationListener(mEdgeGestureActivationListener, - flags); - mLastEdgePositions = flags; - } - } - IStatusBarService getStatusBarService() { synchronized (mServiceAquireLock) { if (mStatusBarService == null) { @@ -2813,19 +2746,6 @@ public void updateSettings() { mUserRotationAngles = Settings.System.getInt(resolver, Settings.System.ACCELEROMETER_ROTATION_ANGLES, -1); - final boolean useEdgeService = Settings.System.getIntForUser(resolver, - Settings.System.USE_EDGE_SERVICE_FOR_GESTURES, 1, UserHandle.USER_CURRENT) == 1; - if (useEdgeService ^ mUsingEdgeGestureServiceForGestures && mSystemReady) { - if (!mUsingEdgeGestureServiceForGestures && useEdgeService) { - mUsingEdgeGestureServiceForGestures = true; - mWindowManagerFuncs.unregisterPointerEventListener(mSystemGestures); - } else if (mUsingEdgeGestureServiceForGestures && !useEdgeService) { - mUsingEdgeGestureServiceForGestures = false; - mWindowManagerFuncs.registerPointerEventListener(mSystemGestures); - } - updateEdgeGestureListenerState(); - } - if (mSystemReady) { int pointerLocation = Settings.System.getIntForUser(resolver, Settings.System.POINTER_LOCATION, 0, UserHandle.USER_CURRENT); @@ -4878,8 +4798,6 @@ public int adjustSystemUiVisibilityLw(int visibility) { mStatusBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility); mNavigationBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility); - updateEdgeGestureListenerState(); - // Reset any bits in mForceClearingStatusBarVisibility that // are now clear. mResettingSystemUiFlags &= visibility; @@ -6269,7 +6187,6 @@ public int finishPostLayoutPolicyLw() { // update since mAllowLockscreenWhenOn might have changed updateLockScreenTimeout(); - updateEdgeGestureListenerState(); return changes; } @@ -8331,13 +8248,6 @@ public void systemReady() { mANBIHandler = new ANBIHandler(mContext); - mEdgeGestureManager = EdgeGestureManager.getInstance(); - mEdgeGestureManager.setEdgeGestureActivationListener(mEdgeGestureActivationListener); - - // Ensure observe happens in systemReady() since we need - // LineageHardwareService to be up and running - mSettingsObserver.observe(); - readCameraLensCoverState(); updateUiMode(); synchronized (mLock) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index d55ce5a4e9b62..7d9517a22a924 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -91,7 +91,6 @@ import com.android.server.os.SchedulingPolicyService; import com.android.server.pocket.PocketService; import com.android.server.pm.BackgroundDexOptService; -import com.android.server.gesture.EdgeGestureService; import com.android.server.pm.Installer; import com.android.server.pm.LauncherAppsService; import com.android.server.pm.OtaDexoptService; @@ -910,7 +909,6 @@ private void startOtherServices() { ILockSettings lockSettings = null; MediaRouterService mediaRouter = null; GestureService gestureService = null; - EdgeGestureService edgeGestureService = null; // Bring up services needed for UI. if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) { @@ -1542,14 +1540,6 @@ private void startOtherServices() { Slog.i(TAG, "Starting PocketService"); mSystemServiceManager.startService(PocketService.class); - traceBeginAndSlog("EdgeGestureService"); - try { - edgeGestureService = new EdgeGestureService(context, inputManager); - ServiceManager.addService("edgegestureservice", edgeGestureService); - } catch (Throwable e) { - Slog.e(TAG, "Failure starting EdgeGesture service", e); - } - traceEnd(); } if (!disableNonCoreServices && !disableMediaProjection) { @@ -1700,11 +1690,11 @@ private void startOtherServices() { mSystemServiceManager.setSafeMode(safeMode); - if (edgeGestureService != null) { + if (gestureService != null) { try { - edgeGestureService.systemReady(); + gestureService.systemReady(); } catch (Throwable e) { - reportWtf("making EdgeGesture service ready", e); + reportWtf("making Gesture Sensor Service ready", e); } } diff --git a/services/java/com/android/server/gesture/EdgeGestureInputFilter.java b/services/java/com/android/server/gesture/EdgeGestureInputFilter.java deleted file mode 100644 index c42b7d098b45d..0000000000000 --- a/services/java/com/android/server/gesture/EdgeGestureInputFilter.java +++ /dev/null @@ -1,539 +0,0 @@ -/* - * Copyright (C) 2013 The CyanogenMod Project (Jens Doll) - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.android.server.gesture; - -import android.content.Context; -import android.content.res.Resources; -import android.os.Handler; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.SystemClock; -import android.os.Trace; -import android.util.Slog; -import android.view.Display; -import android.view.DisplayInfo; -import android.view.IInputFilter; -import android.view.IInputFilterHost; -import android.view.InputDevice; -import android.view.InputEvent; -import android.view.MotionEvent; -import android.view.WindowManagerPolicy; -import android.view.MotionEvent.PointerCoords; -import android.view.MotionEvent.PointerProperties; - -import com.android.internal.R; -import com.android.internal.util.gesture.EdgeGesturePosition; -import com.android.server.gesture.EdgeGestureTracker.OnActivationListener; - -import java.io.PrintWriter; - -/** - * A simple input filter, that listens for edge swipe gestures in the motion event input - * stream. - *

    - * There are 5 distinct states of this filter. - * 1) LISTEN: - * mTracker.active == false - * All motion events are passed through. If a ACTION_DOWN within a gesture trigger area happen - * switch to DETECTING. - * 2) DETECTING: - * mTracker.active == true - * All events are buffered now, and the gesture is checked by mTracker. If mTracker rejects - * the gesture (hopefully as fast as possible) all cached events will be flushed out and the - * filter falls back to LISTEN. - * If mTracker accepts the gesture, clear all cached events and go to LOCKED. - * 3) LOCKED: - * mTracker.active == false - * All events will be cached until the state changes to SYNTHESIZE through a filter - * unlock event. If there is a ACTION_UP, _CANCEL or any PointerId differently to the last - * event seen when mTracker accepted the gesture, we flush all events and go to LISTEN. - * 4) SYNTHESIZE: - * The first motion event found will be turned into a ACTION_DOWN event, all previous events - * will be discarded. - * 5) POSTSYNTHESIZE: - * mSyntheticDownTime != -1 - * All following events will have the down time set to the synthesized ACTION_DOWN event time - * until an ACTION_UP or ACTION_CANCEL is encountered and the state is reset to LISTEN. - * 6) DROP: - * All following events will be discarded. If there is an ACTION_UP, _CANCEL - * we go to LISTEN state. - *

    - * If you are reading this within Java Doc, you are doing something wrong ;) - */ -public class EdgeGestureInputFilter implements IInputFilter { - /* WARNING!! The IInputFilter interface is used directly, there is no Binder between this and - * the InputDispatcher. - * This is fine, because it prevents unnecessary parceling, but beware: - * This means we are running on the dispatch or listener thread of the input dispatcher. Every - * cycle we waste here adds to the overall input latency. - */ - private static final String TAG = "EdgeGestureInputFilter"; - private static final boolean DEBUG = false; - private static final boolean DEBUG_INPUT = false; - // TODO: Should be turned off in final commit - private static final boolean SYSTRACE = false; - - private final Handler mHandler; - - private IInputFilterHost mHost = null; // dispatcher thread - - private static final class MotionEventInfo { - private static final int MAX_POOL_SIZE = 16; - - private static final Object sLock = new Object(); - private static MotionEventInfo sPool; - private static int sPoolSize; - - private boolean mInPool; - - public static MotionEventInfo obtain(MotionEvent event, int policyFlags) { - synchronized (sLock) { - MotionEventInfo info; - if (sPoolSize > 0) { - sPoolSize--; - info = sPool; - sPool = info.next; - info.next = null; - info.mInPool = false; - } else { - info = new MotionEventInfo(); - } - info.initialize(event, policyFlags); - return info; - } - } - - private void initialize(MotionEvent event, int policyFlags) { - this.event = MotionEvent.obtain(event); - this.policyFlags = policyFlags; - cachedTimeMillis = SystemClock.uptimeMillis(); - } - - public void recycle() { - synchronized (sLock) { - if (mInPool) { - throw new IllegalStateException("Already recycled."); - } - clear(); - if (sPoolSize < MAX_POOL_SIZE) { - sPoolSize++; - next = sPool; - sPool = this; - mInPool = true; - } - } - } - - private void clear() { - event.recycle(); - event = null; - policyFlags = 0; - } - - public MotionEventInfo next; - public MotionEvent event; - public int policyFlags; - public long cachedTimeMillis; - } - private final Object mLock = new Object(); - private MotionEventInfo mMotionEventQueue; // guarded by mLock - private MotionEventInfo mMotionEventQueueTail; // guarded by mLock - /* DEBUG */ - private int mMotionEventQueueCountDebug; // guarded by mLock - - private int mDeviceId; // dispatcher only - private enum State { - LISTEN, DETECTING, LOCKED, SYNTHESIZE, POSTSYNTHESIZE, DROP; - } - private State mState = State.LISTEN; // guarded by mLock - private EdgeGestureTracker mTracker; // guarded by mLock - private volatile int mPositions; // written by handler / read by dispatcher - private volatile int mSensitivity; // written by handler / read by dispatcher - - // only used by dispatcher - private long mSyntheticDownTime = -1; - private PointerCoords[] mTempPointerCoords = new PointerCoords[1]; - private PointerProperties[] mTempPointerProperties = new PointerProperties[1]; - - public EdgeGestureInputFilter(Context context, Handler handler) { - mHandler = handler; - - final Resources res = context.getResources(); - mTracker = new EdgeGestureTracker(res.getDimensionPixelSize( - R.dimen.edge_gesture_trigger_thickness), - res.getDimensionPixelSize(R.dimen.edge_gesture_trigger_distance), - res.getDimensionPixelSize(R.dimen.edge_gesture_perpendicular_distance)); - mTracker.setOnActivationListener(new OnActivationListener() { - public void onActivation(MotionEvent event, int touchX, int touchY, EdgeGesturePosition position) { - // mLock is held by #processMotionEvent - mHandler.obtainMessage(EdgeGestureService.MSG_EDGE_GESTURE_ACTIVATION, - touchX, touchY, position).sendToTarget(); - mState = State.LOCKED; - } - }); - mTempPointerCoords[0] = new PointerCoords(); - mTempPointerProperties[0] = new PointerProperties(); - } - - // called from handler thread (lock taken) - public void updateDisplay(Display display, DisplayInfo displayInfo) { - synchronized (mLock) { - mTracker.updateDisplay(display); - } - } - - // called from handler thread (lock taken) - public void updatePositions(int positions) { - mPositions = positions; - } - - // called from handler thread (lock taken) - public void updateSensitivity(int sensitivity) { - mSensitivity = sensitivity; - } - - // called from handler thread - public boolean unlockFilter() { - synchronized (mLock) { - if (mState == State.LOCKED) { - mState = State.SYNTHESIZE; - return true; - } - } - return false; - } - - public boolean dropSequence() { - synchronized (mLock) { - if (mState == State.LOCKED) { - mState = State.DROP; - return true; - } - } - return false; - } - - /** - * Called to enqueue the input event for filtering. - * The event must be recycled after the input filter processed it. - * This method is guaranteed to be non-reentrant. - * - * @see InputFilter#filterInputEvent(InputEvent, int) - * @param event The input event to enqueue. - */ - // called by the input dispatcher thread - public void filterInputEvent(InputEvent event, int policyFlags) throws RemoteException { - if (SYSTRACE) { - Trace.traceBegin(Trace.TRACE_TAG_INPUT, "filterInputEvent"); - } - try { - if (((event.getSource() & InputDevice.SOURCE_TOUCHSCREEN) - != InputDevice.SOURCE_TOUCHSCREEN) - || !(event instanceof MotionEvent)) { - sendInputEvent(event, policyFlags); - return; - } - if (DEBUG_INPUT) { - Slog.d(TAG, "Received event: " + event + ", policyFlags=0x" - + Integer.toHexString(policyFlags)); - } - MotionEvent motionEvent = (MotionEvent) event; - final int deviceId = event.getDeviceId(); - if (deviceId != mDeviceId) { - processDeviceSwitch(deviceId, motionEvent, policyFlags); - } else { - if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) { - synchronized (mLock) { - clearAndResetStateLocked(false, true); - } - } - processMotionEvent(motionEvent, policyFlags); - } - } finally { - event.recycle(); - if (SYSTRACE) { - Trace.traceEnd(Trace.TRACE_TAG_INPUT); - } - } - } - - private void processDeviceSwitch(int deviceId, MotionEvent motionEvent, int policyFlags) { - if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { - mDeviceId = deviceId; - synchronized (mLock) { - clearAndResetStateLocked(true, false); - processMotionEvent(motionEvent, policyFlags); - } - } else { - sendInputEvent(motionEvent, policyFlags); - } - } - - private void processMotionEvent(MotionEvent motionEvent, int policyFlags) { - final int action = motionEvent.getActionMasked(); - - synchronized (mLock) { - switch (mState) { - case LISTEN: - if (action == MotionEvent.ACTION_DOWN) { - boolean hit = mPositions != 0 - && mTracker.start(motionEvent, mPositions, mSensitivity); - if (DEBUG) Slog.d(TAG, "start:" + hit); - if (hit) { - // cache the down event - cacheDelayedMotionEventLocked(motionEvent, policyFlags); - mState = State.DETECTING; - return; - } - } - sendInputEvent(motionEvent, policyFlags); - break; - case DETECTING: - cacheDelayedMotionEventLocked(motionEvent, policyFlags); - if (action == MotionEvent.ACTION_MOVE) { - if (mTracker.move(motionEvent)) { - // return: the tracker is either detecting or triggered onActivation - return; - } - } - if (DEBUG) { - Slog.d(TAG, "move: reset!"); - } - clearAndResetStateLocked(false, true); - break; - case LOCKED: - cacheDelayedMotionEventLocked(motionEvent, policyFlags); - if (action != MotionEvent.ACTION_MOVE) { - clearAndResetStateLocked(false, true); - } - break; - case SYNTHESIZE: - if (action == MotionEvent.ACTION_MOVE) { - clearDelayedMotionEventsLocked(); - sendSynthesizedMotionEventLocked(motionEvent, policyFlags); - mState = State.POSTSYNTHESIZE; - } else { - // This is the case where a race condition caught us: We already - // returned the handler thread that it is all right to call - // #gainTouchFocus(), but apparently this was wrong, as the gesture - // was canceled now. - clearAndResetStateLocked(false, true); - } - break; - case POSTSYNTHESIZE: - motionEvent.setDownTime(mSyntheticDownTime); - if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { - mState = State.LISTEN; - mSyntheticDownTime = -1; - } - sendInputEvent(motionEvent, policyFlags); - break; - case DROP: - if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { - clearDelayedMotionEventsLocked(); - mState = State.LISTEN; - } - break; - } - } - } - - private void clearAndResetStateLocked(boolean force, boolean shift) { - // ignore soft reset in POSTSYNTHESIZE, because we need to tamper with - // the event stream and going to LISTEN after an ACTION_UP anyway - if (!force && (mState == State.POSTSYNTHESIZE)) { - return; - } - switch (mState) { - case LISTEN: - // this is a nop - break; - case DETECTING: - mTracker.reset(); - // intentionally no break here - case LOCKED: - case SYNTHESIZE: - sendDelayedMotionEventsLocked(shift); - break; - case POSTSYNTHESIZE: - // hard reset (this will break the event stream) - Slog.w(TAG, "Quit POSTSYNTHESIZE without ACTION_UP from ACTION_DOWN at " - + mSyntheticDownTime); - mSyntheticDownTime = -1; - break; - } - // if there are future events that need to be tampered with, goto POSTSYNTHESIZE - mState = mSyntheticDownTime == -1 ? State.LISTEN : State.POSTSYNTHESIZE; - } - - private void sendInputEvent(InputEvent event, int policyFlags) { - try { - mHost.sendInputEvent(event, policyFlags); - } catch (RemoteException e) { - /* ignore */ - } - } - - private void cacheDelayedMotionEventLocked(MotionEvent event, int policyFlags) { - MotionEventInfo info = MotionEventInfo.obtain(event, policyFlags); - if (mMotionEventQueue == null) { - mMotionEventQueue = info; - } else { - mMotionEventQueueTail.next = info; - } - mMotionEventQueueTail = info; - mMotionEventQueueCountDebug++; - if (SYSTRACE) { - Trace.traceCounter(Trace.TRACE_TAG_INPUT, "meq", mMotionEventQueueCountDebug); - } - } - - private void sendDelayedMotionEventsLocked(boolean shift) { - while (mMotionEventQueue != null) { - MotionEventInfo info = mMotionEventQueue; - mMotionEventQueue = info.next; - - if (DEBUG) { - Slog.d(TAG, "Replay event: " + info.event); - } - mMotionEventQueueCountDebug--; - if (SYSTRACE) { - Trace.traceCounter(Trace.TRACE_TAG_INPUT, "meq", mMotionEventQueueCountDebug); - } - if (shift) { - final long offset = SystemClock.uptimeMillis() - info.cachedTimeMillis; - if (info.event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mSyntheticDownTime = info.event.getDownTime() + offset; - } - sendMotionEventWithOffsetLocked(info.event, info.policyFlags, mSyntheticDownTime, offset); - if (info.event.getActionMasked() == MotionEvent.ACTION_UP) { - mSyntheticDownTime = -1; - } - } else { - sendInputEvent(info.event, info.policyFlags); - } - info.recycle(); - } - mMotionEventQueueTail = null; - } - - private void clearDelayedMotionEventsLocked() { - while (mMotionEventQueue != null) { - MotionEventInfo next = mMotionEventQueue.next; - mMotionEventQueue.recycle(); - mMotionEventQueue = next; - } - mMotionEventQueueTail = null; - mMotionEventQueueCountDebug = 0; - if (SYSTRACE) { - Trace.traceCounter(Trace.TRACE_TAG_INPUT, "meq", mMotionEventQueueCountDebug); - } - } - - private void sendMotionEventWithOffsetLocked(MotionEvent event, int policyFlags, - long downTime, long offset) { - final int pointerCount = event.getPointerCount(); - PointerCoords[] coords = getTempPointerCoordsWithMinSizeLocked(pointerCount); - PointerProperties[] properties = getTempPointerPropertiesWithMinSizeLocked(pointerCount); - for (int i = 0; i < pointerCount; i++) { - event.getPointerCoords(i, coords[i]); - event.getPointerProperties(i, properties[i]); - } - final long eventTime = event.getEventTime() + offset; - sendInputEvent(MotionEvent.obtain(downTime, eventTime, event.getAction(), pointerCount, - properties, coords, event.getMetaState(), event.getButtonState(), 1.0f, 1.0f, - event.getDeviceId(), event.getEdgeFlags(), event.getSource(), event.getFlags()), - policyFlags); - } - - private PointerCoords[] getTempPointerCoordsWithMinSizeLocked(int size) { - final int oldSize = mTempPointerCoords.length; - if (oldSize < size) { - PointerCoords[] oldTempPointerCoords = mTempPointerCoords; - mTempPointerCoords = new PointerCoords[size]; - System.arraycopy(oldTempPointerCoords, 0, mTempPointerCoords, 0, oldSize); - } - for (int i = oldSize; i < size; i++) { - mTempPointerCoords[i] = new PointerCoords(); - } - return mTempPointerCoords; - } - - private PointerProperties[] getTempPointerPropertiesWithMinSizeLocked(int size) { - final int oldSize = mTempPointerProperties.length; - if (oldSize < size) { - PointerProperties[] oldTempPointerProperties = mTempPointerProperties; - mTempPointerProperties = new PointerProperties[size]; - System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, oldSize); - } - for (int i = oldSize; i < size; i++) { - mTempPointerProperties[i] = new PointerProperties(); - } - return mTempPointerProperties; - } - - private void sendSynthesizedMotionEventLocked(MotionEvent event, int policyFlags) { - if (event.getPointerCount() == 1) { - event.getPointerCoords(0, mTempPointerCoords[0]); - event.getPointerProperties(0, mTempPointerProperties[0]); - MotionEvent down = MotionEvent.obtain(event.getEventTime(), event.getEventTime(), - MotionEvent.ACTION_DOWN, 1, mTempPointerProperties, mTempPointerCoords, - event.getMetaState(), event.getButtonState(), - 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(), - event.getSource(), event.getFlags()); - Slog.d(TAG, "Synthesized event:" + down); - sendInputEvent(down, policyFlags); - mSyntheticDownTime = event.getEventTime(); - } else { - Slog.w(TAG, "Could not synthesize MotionEvent, this will drop all following events!"); - } - } - - // should never be called - public IBinder asBinder() { - throw new UnsupportedOperationException(); - } - - // called by the input dispatcher thread - public void install(IInputFilterHost host) throws RemoteException { - if (DEBUG) { - Slog.d(TAG, "EdgeGesture input filter installed."); - } - mHost = host; - synchronized (mLock) { - clearAndResetStateLocked(true, false); - } - } - - // called by the input dispatcher thread - public void uninstall() throws RemoteException { - if (DEBUG) { - Slog.d(TAG, "EdgeGesture input filter uninstalled."); - } - } - - // called by a Binder thread - public void dump(PrintWriter pw, String prefix) { - synchronized (mLock) { - pw.print(prefix); - pw.println("mState=" + mState.name()); - pw.print(prefix); - pw.println("mPositions=0x" + Integer.toHexString(mPositions)); - pw.print(prefix); - pw.println("mQueue=" + mMotionEventQueueCountDebug + " items"); - } - } -} diff --git a/services/java/com/android/server/gesture/EdgeGestureService.java b/services/java/com/android/server/gesture/EdgeGestureService.java deleted file mode 100644 index 9dc9ac859260c..0000000000000 --- a/services/java/com/android/server/gesture/EdgeGestureService.java +++ /dev/null @@ -1,486 +0,0 @@ -/* - * Copyright (C) 2013 The CyanogenMod Project (Jens Doll) - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.android.server.gesture; - -import static com.android.internal.util.gesture.EdgeServiceConstants.POSITION_MASK; -import static com.android.internal.util.gesture.EdgeServiceConstants.SENSITIVITY_DEFAULT; -import static com.android.internal.util.gesture.EdgeServiceConstants.SENSITIVITY_MASK; -import static com.android.internal.util.gesture.EdgeServiceConstants.SENSITIVITY_NONE; -import static com.android.internal.util.gesture.EdgeServiceConstants.SENSITIVITY_SHIFT; -import static com.android.internal.util.gesture.EdgeServiceConstants.LONG_LIVING; - -import android.Manifest; -import android.content.Context; -import android.content.pm.PackageManager; -import android.hardware.display.DisplayManager; -import android.hardware.display.DisplayManager.DisplayListener; -import android.os.Binder; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.Parcel; -import android.os.RemoteException; -import android.service.gesture.IEdgeGestureActivationListener; -import android.service.gesture.IEdgeGestureHostCallback; -import android.service.gesture.IEdgeGestureService; -import android.util.Slog; -import android.view.Display; -import android.view.DisplayInfo; -import android.view.WindowManager; - -import com.android.internal.util.gesture.EdgeGesturePosition; -import com.android.server.gesture.EdgeGestureInputFilter; -import com.android.server.input.InputManagerService; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; - -/** - * A system service to track and handle edge swipe gestures. This service interacts with - * the {@link InputManagerService} to do all the dirty work for listeners: - *

  • Installing an input filter and listen for edge swipe gestures
  • - *
  • Removing those gestures from the input stream
  • - *
  • Transferring touch focus to new recipient
  • - */ -public class EdgeGestureService extends IEdgeGestureService.Stub { - public static final String TAG = "EdgeGestureService"; - public static final boolean DEBUG = false; - public static final boolean DEBUG_INPUT = false; - - public static final int MSG_EDGE_GESTURE_ACTIVATION = 32023; - public static final int MSG_UPDATE_SERVICE = 32025; - - private final Context mContext; - private final InputManagerService mInputManager; - - private final HandlerThread mHandlerThread = new HandlerThread("EdgeGestureHandler"); - private Handler mHandler; - - // Lock for mInputFilter, activations and listener related variables - private final Object mLock = new Object(); - private EdgeGestureInputFilter mInputFilter; - - private int mGlobalPositions = 0; - private int mGlobalSensitivity = 3; - - private final class EdgeGestureActivationListenerRecord extends IEdgeGestureHostCallback.Stub implements DeathRecipient { - private boolean mActive; - - public EdgeGestureActivationListenerRecord(IEdgeGestureActivationListener listener) { - this.listener = listener; - this.positions = 0; - } - - public void binderDied() { - removeListenerRecord(this); - } - - private void updateFlags(int flags) { - this.positions = flags & POSITION_MASK; - this.sensitivity = (flags & SENSITIVITY_MASK) >> SENSITIVITY_SHIFT; - this.longLiving = (flags & LONG_LIVING) != 0; - } - - private boolean eligibleForActivation(int positionFlag) { - return (positions & positionFlag) != 0; - } - - private boolean notifyEdgeGestureActivation(int touchX, int touchY, EdgeGesturePosition position) { - if ((positions & position.FLAG) != 0) { - try { - mActive = true; - listener.onEdgeGestureActivation(touchX, touchY, position.INDEX, 0); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to notify process, assuming it died.", e); - mActive = false; - binderDied(); - } - } - return mActive; - } - - // called through Binder - public boolean gainTouchFocus(IBinder windowToken) { - if (DEBUG) { - Slog.d(TAG, "Gain touch focus for " + windowToken); - } - if (mActive) { - return mInputFilter.unlockFilter(); - } - return false; - } - - public boolean dropEventsUntilLift() { - if (DEBUG) { - Slog.d(TAG, "Will drop all next events till touch up"); - } - if (mActive) { - return mInputFilter.dropSequence(); - } - return false; - } - - // called through Binder - public void restoreListenerState() throws RemoteException { - if (DEBUG) { - Slog.d(TAG, "Restore listener state"); - } - if (mActive) { - mInputFilter.unlockFilter(); - mActive = false; - synchronized (mLock) { - // restore input filter state by updating - mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE); - } - } - } - - public boolean isActive() { - return mActive; - } - - public void dump(PrintWriter pw, String prefix) { - pw.print(prefix); - pw.print("mPositions=0x" + Integer.toHexString(positions)); - pw.println(" mActive=" + mActive); - pw.print(prefix); - pw.println("mBinder=" + listener); - } - - public int positions; - public int sensitivity; - public final IEdgeGestureActivationListener listener; - public boolean longLiving = false; - } - private final List mEdgeGestureActivationListener = - new ArrayList(); - // end of lock guarded variables - - private DisplayObserver mDisplayObserver; - - // called by system server - public EdgeGestureService(Context context, InputManagerService inputManager) { - mContext = context; - mInputManager = inputManager; - } - - // called by system server - public void systemReady() { - if (DEBUG) Slog.d(TAG, "Starting the edge gesture capture thread ..."); - - mHandlerThread.start(); - mHandler = new H(mHandlerThread.getLooper()); - mHandler.post(new Runnable() { - @Override - public void run() { - android.os.Process.setThreadPriority( - android.os.Process.THREAD_PRIORITY_FOREGROUND); - android.os.Process.setCanSelfBackground(false); - } - }); - mDisplayObserver = new DisplayObserver(mContext, mHandler); - // check if anyone registered during startup - mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE); - } - - - private void updateMonitoring() { - synchronized(mLock) { - mGlobalPositions = 0; - mGlobalSensitivity = SENSITIVITY_NONE; - boolean someLongLiving = false; - int activePositions = 0; - for (EdgeGestureActivationListenerRecord temp : mEdgeGestureActivationListener) { - mGlobalPositions |= temp.positions; - if (temp.isActive()) { - activePositions |= temp.positions; - } - if (temp.sensitivity != SENSITIVITY_NONE) { - mGlobalSensitivity = Math.max(mGlobalSensitivity, temp.sensitivity); - } - someLongLiving |= temp.longLiving; - } - boolean havePositions = mGlobalPositions != 0; - mGlobalPositions &= ~activePositions; - // if no special sensitivity is requested, we settle on DEFAULT - if (mGlobalSensitivity == SENSITIVITY_NONE) { - mGlobalSensitivity = SENSITIVITY_DEFAULT; - } - - if (mInputFilter == null && havePositions) { - enforceMonitoringLocked(); - } else if (mInputFilter != null && !havePositions && !someLongLiving) { - shutdownMonitoringLocked(); - } - } - } - - private void enforceMonitoringLocked() { - if (DEBUG) { - Slog.d(TAG, "Attempting to start monitoring input events ..."); - } - mInputFilter = new EdgeGestureInputFilter(mContext, mHandler); - mInputManager.setInputFilter(mInputFilter); - mDisplayObserver.observe(); - } - - private void shutdownMonitoringLocked() { - if (DEBUG) { - Slog.d(TAG, "Shutting down monitoring input events ..."); - } - mDisplayObserver.unobserve(); - mInputManager.setInputFilter(mInputFilter); - mInputFilter = null; - } - - // called through Binder - public IEdgeGestureHostCallback registerEdgeGestureActivationListener(IEdgeGestureActivationListener listener) { - if (mContext.checkCallingOrSelfPermission(Manifest.permission.INJECT_EVENTS) - != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission Denial: can't register from from pid=" - + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); - return null; - } - - if (listener == null) { - throw new IllegalArgumentException("listener must not be null"); - } - - EdgeGestureActivationListenerRecord record = null; - synchronized(mLock) { - record = findListenerRecordLocked(listener.asBinder()); - if (record == null) { - record = new EdgeGestureActivationListenerRecord(listener); - try { - listener.asBinder().linkToDeath(record, 0); - } catch (RemoteException e) { - Slog.w(TAG, "Recipient died during registration pid=" + Binder.getCallingPid()); - return null; - } - mEdgeGestureActivationListener.add(record); - } - } - return record; - } - - // called through Binder - public void updateEdgeGestureActivationListener(IBinder listener, int positionFlags) { - if (listener == null) { - throw new IllegalArgumentException("listener must not be null"); - } - synchronized(mLock) { - EdgeGestureActivationListenerRecord record = findListenerRecordLocked(listener); - if (record == null) { - Slog.w(TAG, "Unknown listener on update listener. Register first?"); - throw new IllegalStateException("listener not registered"); - } - record.updateFlags(positionFlags); - // update input filter only when #systemReady() was called - if (mHandler != null) { - mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE); - } - } - } - - private EdgeGestureActivationListenerRecord findListenerRecordLocked(IBinder listener) { - for (EdgeGestureActivationListenerRecord record : mEdgeGestureActivationListener) { - if (record.listener.asBinder().equals(listener)) { - return record; - } - } - return null; - } - - private void removeListenerRecord(EdgeGestureActivationListenerRecord record) { - synchronized(mLock) { - mEdgeGestureActivationListener.remove(record); - // restore input filter state by updating - mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE); - } - } - - // called by handler thread - private boolean propagateActivation(int touchX, int touchY, EdgeGesturePosition position) { - synchronized(mLock) { - EdgeGestureActivationListenerRecord target = null; - for (EdgeGestureActivationListenerRecord record : mEdgeGestureActivationListener) { - if (record.eligibleForActivation(position.FLAG)) { - target = record; - break; - } - } - // NOTE: We can do this here because the #onGestureActivation() is a oneway - // Binder call. This means we do not block with holding the mListenerLock!!! - // If this ever change, this needs to be adjusted and if you don't know what - // this means, you should probably not mess around with this code, anyway. - if (target != null && !target.notifyEdgeGestureActivation(touchX, touchY, position)) { - target = null; - } - return target != null; - } - } - - private final class H extends Handler { - public H(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message m) { - switch (m.what) { - case MSG_EDGE_GESTURE_ACTIVATION: - if (DEBUG) { - Slog.d(TAG, "Activating edge gesture on " + m.obj.toString()); - } - // Since input filter runs asynchronously to us, double activation may happen - // theoretically. Take the safe route here. - removeMessages(MSG_EDGE_GESTURE_ACTIVATION); - if (propagateActivation(m.arg1, m.arg2, (EdgeGesturePosition) m.obj)) { - // switch off activated positions - updateMonitoring(); - updateServiceHandler(mGlobalPositions, mGlobalSensitivity); - } - break; - case MSG_UPDATE_SERVICE: - updateMonitoring(); - if (DEBUG) { - Slog.d(TAG, "Updating positions 0x" + Integer.toHexString(mGlobalPositions) - + " sensitivity: " + mGlobalSensitivity); - } - updateServiceHandler(mGlobalPositions, mGlobalSensitivity); - break; - } - } - - private void updateServiceHandler(int positions, int sensitivity) { - synchronized (mLock) { - if (mInputFilter != null) { - mInputFilter.updatePositions(positions); - mInputFilter.updateSensitivity(sensitivity); - } - } - } - } - - private final class DisplayObserver implements DisplayListener { - private final Handler mHandler; - private final DisplayManager mDisplayManager; - - private final Display mDefaultDisplay; - private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo(); - - public DisplayObserver(Context context, Handler handler) { - mHandler = handler; - mDisplayManager = (DisplayManager) context.getSystemService( - Context.DISPLAY_SERVICE); - final WindowManager windowManager = (WindowManager) context.getSystemService( - Context.WINDOW_SERVICE); - - mDefaultDisplay = windowManager.getDefaultDisplay(); - updateDisplayInfo(); - } - - private void updateDisplayInfo() { - if (DEBUG) { - Slog.d(TAG, "Updating display information ..."); - } - if (mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) { - synchronized (mLock) { - if (mInputFilter != null) { - mInputFilter.updateDisplay(mDefaultDisplay, mDefaultDisplayInfo); - } - } - } else { - Slog.e(TAG, "Default display is not valid."); - } - } - - public void observe() { - mDisplayManager.registerDisplayListener(this, mHandler); - updateDisplayInfo(); - } - - public void unobserve() { - mDisplayManager.unregisterDisplayListener(this); - } - - @Override - public void onDisplayAdded(int displayId) { - /* do noting */ - } - - @Override - public void onDisplayRemoved(int displayId) { - /* do nothing */ - } - - @Override - public void onDisplayChanged(int displayId) { - updateDisplayInfo(); - } - } - - @Override - public boolean onTransact(int code, Parcel data, Parcel reply, int flags) - throws RemoteException { - try { - return super.onTransact(code, data, reply, flags); - } catch (RuntimeException e) { - // let's log all exceptions we do not know about. - if (!(e instanceof IllegalArgumentException || e instanceof IllegalStateException)) { - Slog.e(TAG, "EdgeGestureService crashed: ", e); - } - throw e; - } - } - - @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) - != PackageManager.PERMISSION_GRANTED) { - pw.println("Permission Denial: can't dump EdgeGestureService from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); - return; - } - - pw.println("EDGE GESTURE SERVICE (dumpsys edgegestureservice)\n"); - synchronized(mLock) { - pw.println(" mInputFilter=" + mInputFilter); - if (mInputFilter != null) { - mInputFilter.dump(pw, " "); - } - pw.println(" mGlobalPositions=0x" + Integer.toHexString(mGlobalPositions)); - pw.println(" mGlobalSensitivity=" + mGlobalSensitivity); - int i = 0; - for (EdgeGestureActivationListenerRecord record : mEdgeGestureActivationListener) { - if (record.isActive()) { - pw.println(" Active record: #" + (i + 1)); - } - } - i = 0; - for (EdgeGestureActivationListenerRecord record : mEdgeGestureActivationListener) { - pw.println(" Listener #" + i + ":"); - record.dump(pw, " "); - i++; - } - } - } -} diff --git a/services/java/com/android/server/gesture/EdgeGestureTracker.java b/services/java/com/android/server/gesture/EdgeGestureTracker.java deleted file mode 100644 index 17cd95eda0d0b..0000000000000 --- a/services/java/com/android/server/gesture/EdgeGestureTracker.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2013 The CyanogenMod Project (Jens Doll) - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.android.server.gesture; - -import android.graphics.Point; -import android.os.SystemClock; -import android.util.Slog; -import android.view.Display; -import android.view.MotionEvent; - -import com.android.internal.util.gesture.EdgeGesturePosition; -import com.android.internal.util.gesture.EdgeServiceConstants; - -/** - * A simple {@link MotionEvent} tracker class. The main aim of this tracker is to - * reject gestures as fast as possible, so there is only a small amount of events - * that will be delayed. - */ -public class EdgeGestureTracker { - public final static String TAG = "EdgeGestureTracker"; - public final static boolean DEBUG = false; - - public final static long TRIGGER_TIME_MS = 140; - public final static int PIXEL_SWIPE_OFFTAKE_SLOP = 2; - - private final int mBaseThickness; - private final int mBaseTriggerDistance; - private final int mBasePerpendicularDistance; - - private int mThickness; - private int mTriggerDistance; - private int mPerpendicularDistance; - private int mGracePeriodDistance; - private long mTimeOut; - - private int mDisplayWidth; - private int mDisplayHeight; - - private boolean mActive; - private EdgeGesturePosition mPosition; - private long mDownTime; - private int mInitialX; - private int mInitialY; - private int mOffTake; - private int mGracePeriod; - - public interface OnActivationListener { - public void onActivation(MotionEvent event, int touchX, int touchY, EdgeGesturePosition position); - } - private OnActivationListener mActivationListener; - - public EdgeGestureTracker(int thickness, int distance, int perpendicular) { - if (DEBUG) { - Slog.d(TAG, "init: " + thickness + "," + distance); - } - mBaseThickness = thickness; - mBaseTriggerDistance = distance; - mBasePerpendicularDistance = perpendicular; - setSensitivity(0); - } - - private void setSensitivity(int sensitivity) { - float factor = 0.0f; - if (sensitivity >= 1) { - factor = (sensitivity - 1) / 4.0f; - } - if (DEBUG) { - Slog.d(TAG, "sensitivity: " + sensitivity + " => factor:" + factor); - } - // default values (without overlay): - // 140ms ... 210ms - mTimeOut = (long) (TRIGGER_TIME_MS * (factor + 1.0f)); - // 12dp ... 18dp - mThickness = (int) (mBaseThickness * (factor + 1.0f)); - // 12dp ... 6dp - mTriggerDistance = (int) (mBaseTriggerDistance * (1.0f - factor)); - mPerpendicularDistance = (int) (mBasePerpendicularDistance * (1.0f - factor)); - mGracePeriodDistance = (int) (mThickness / 3.0f); - } - - public void setOnActivationListener(OnActivationListener listener) { - mActivationListener = listener; - } - - public void reset() { - mActive = false; - } - - public void updateDisplay(Display display) { - Point outSize = new Point(0,0); - display.getRealSize(outSize); - mDisplayWidth = outSize.x; - mDisplayHeight = outSize.y; - if (DEBUG) { - Slog.d(TAG, "new display: " + mDisplayWidth + "," + mDisplayHeight); - } - } - - public boolean start(MotionEvent motionEvent, int positions, int sensitivity) { - final boolean unrestricted = (positions & EdgeServiceConstants.UNRESTRICTED) != 0; - final int x = (int) motionEvent.getX(); - final float fx = motionEvent.getX() / mDisplayWidth; - final int y = (int) motionEvent.getY(); - final float fy = motionEvent.getY() / mDisplayHeight; - - // calculate trigger geometry based on sensitivity - setSensitivity(sensitivity); - - if ((positions & EdgeGesturePosition.LEFT.FLAG) != 0) { - if (x < mThickness && (unrestricted || (fy > 0.1f && fy < 0.9f))) { - startWithPosition(motionEvent, EdgeGesturePosition.LEFT); - return true; - } - } - if ((positions & EdgeGesturePosition.BOTTOM.FLAG) != 0) { - if (y > mDisplayHeight - mThickness && (unrestricted || (fx > 0.1f && fx < 0.9f))) { - startWithPosition(motionEvent, EdgeGesturePosition.BOTTOM); - return true; - } - } - if ((positions & EdgeGesturePosition.RIGHT.FLAG) != 0) { - if (x > mDisplayWidth - mThickness && (unrestricted || (fy > 0.1f && fy < 0.9f))) { - startWithPosition(motionEvent, EdgeGesturePosition.RIGHT); - return true; - } - } - if ((positions & EdgeGesturePosition.TOP.FLAG) != 0) { - if (y < mThickness && (unrestricted || (fx > 0.1f && fx < 0.9f))) { - startWithPosition(motionEvent, EdgeGesturePosition.TOP); - return true; - } - } - return false; - } - - private void startWithPosition(MotionEvent motionEvent, EdgeGesturePosition position) { - if (DEBUG) { - Slog.d(TAG, "start tracking from " + position.name()); - } - - mDownTime = motionEvent.getDownTime(); - this.mPosition = position; - mInitialX = (int) motionEvent.getX(); - mInitialY = (int) motionEvent.getY(); - switch (position) { - case LEFT: - mGracePeriod = mGracePeriodDistance; - mOffTake = mInitialX - PIXEL_SWIPE_OFFTAKE_SLOP; - break; - case BOTTOM: - mOffTake = mInitialY + PIXEL_SWIPE_OFFTAKE_SLOP; - break; - case RIGHT: - mGracePeriod = mDisplayWidth - mGracePeriodDistance; - mOffTake = mInitialX + PIXEL_SWIPE_OFFTAKE_SLOP; - break; - case TOP: - mOffTake = mInitialY - PIXEL_SWIPE_OFFTAKE_SLOP; - break; - } - mActive = true; - } - - public boolean move(MotionEvent motionEvent) { - if (!mActive || motionEvent.getEventTime() - mDownTime > mTimeOut) { - Slog.d(TAG, "edge gesture timeout: " + (motionEvent.getEventTime() - mDownTime)); - mActive = false; - return false; - } - - final int x = (int) motionEvent.getX(); - final int y = (int) motionEvent.getY(); - final int deltaX = x - mInitialX; - final int deltaY = y - mInitialY; - - if (DEBUG) { - Slog.d(TAG, "move at " + x + "," + y + " " + deltaX + "," + deltaY); - } - - boolean loaded = false; - switch (mPosition) { - case LEFT: - if (x < mGracePeriod) { - mInitialY = y; - } - if (deltaY < mPerpendicularDistance && deltaY > -mPerpendicularDistance - && x >= mOffTake) { - if (deltaX < mTriggerDistance) { - mOffTake = x - PIXEL_SWIPE_OFFTAKE_SLOP; - return true; - } - loaded = true; - } - break; - case BOTTOM: - if (deltaX < mPerpendicularDistance && deltaX > -mPerpendicularDistance - && y <= mOffTake) { - if (deltaY > -mTriggerDistance) { - mOffTake = y + PIXEL_SWIPE_OFFTAKE_SLOP; - return true; - } - loaded = true; - } - break; - case RIGHT: - if (x > mGracePeriod) { - mInitialY = y; - } - if (deltaY < mPerpendicularDistance && deltaY > -mPerpendicularDistance - && x <= mOffTake) { - if (deltaX > -mTriggerDistance) { - mOffTake = x + PIXEL_SWIPE_OFFTAKE_SLOP; - return true; - } - loaded = true; - } - break; - case TOP: - if (deltaX < mPerpendicularDistance && deltaX > -mPerpendicularDistance - && y >= mOffTake) { - if (deltaY < mTriggerDistance) { - mOffTake = y - PIXEL_SWIPE_OFFTAKE_SLOP; - return true; - } - loaded = true; - } - break; - } - mActive = false; - if (loaded && mActivationListener != null) { - if (DEBUG) { - Slog.d(TAG, "activate at " + x + "," + y + " " + mPosition + " within " - + (SystemClock.uptimeMillis() - mDownTime) + "ms"); - } - mActivationListener.onActivation(motionEvent, x, y, mPosition); - } - return loaded; - } -} \ No newline at end of file From 1219e3774b68e214987c1d0d7f3d968e0d2b5cba Mon Sep 17 00:00:00 2001 From: Atanas Kirilov Date: Fri, 28 Sep 2018 20:22:08 +0000 Subject: [PATCH 67/73] RESTRICT AUTOMERGE: Revert "RESTRICT AUTOMERGE: Check both self and shared user id package for requested permissions." This reverts commit 05dc947c63a2304adce53a0aef6b0e0a9db9343a. Reason for revert: Not a security fix and the security fix needs this cl is reverted. Bug: 114365189 Change-Id: Id667b1c4d1a1af27837f553d7461283b22e5e41f (cherry picked from commit bb4dcd1099cac4e0b3602e3d2ac088b796df3691) --- .../java/com/android/server/pm/PackageManagerService.java | 5 +---- .../core/java/com/android/server/pm/PermissionsState.java | 8 -------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 828984ff7c15d..d6b572835950b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -284,7 +284,6 @@ import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.PermissionsState.PermissionState; -import com.android.server.pm.PackageSetting; import com.android.server.pm.Settings.DatabaseVersion; import com.android.server.pm.Settings.VersionInfo; import com.android.server.pm.dex.DexManager; @@ -5618,10 +5617,8 @@ public void removePermission(String name) { private static void enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission( PackageParser.Package pkg, BasePermission bp) { - final PackageSetting pkgSetting = (PackageSetting) pkg.mExtras; - final PermissionsState permsState = pkgSetting.getPermissionsState(); int index = pkg.requestedPermissions.indexOf(bp.name); - if (!permsState.hasRequestedPermission(bp.name) && index == -1) { + if (index == -1) { throw new SecurityException("Package " + pkg.packageName + " has not requested permission " + bp.name); } diff --git a/services/core/java/com/android/server/pm/PermissionsState.java b/services/core/java/com/android/server/pm/PermissionsState.java index a14d09ed91725..f4d2ad2c6eb0c 100644 --- a/services/core/java/com/android/server/pm/PermissionsState.java +++ b/services/core/java/com/android/server/pm/PermissionsState.java @@ -290,14 +290,6 @@ public boolean hasRequestedPermission(ArraySet names) { return false; } - /** - * Returns whether the state has any known request for the given permission name, - * whether or not it has been granted. - */ - public boolean hasRequestedPermission(String name) { - return mPermissions != null && (mPermissions.get(name) != null); - } - /** * Gets all permissions for a given device user id regardless if they * are install time or runtime permissions. From 458afeb8987fbbe6edd4436391058357be41b390 Mon Sep 17 00:00:00 2001 From: AmolAmrit Date: Sat, 18 Aug 2018 22:47:28 +0530 Subject: [PATCH 68/73] [Base:1/2] Add back Edge gestures Revert "base: cleanup nav gestures code" This reverts commit ddb5a2fc344530313a0fb979f6d4ba2909b6d321. Revert "base: add long press home handling to nav gestures" This reverts commit b755ddff5be0dd04a3c65c748e815497ebfa40e6. Revert "base: implement OP like navigation gestures [1/2]" This reverts commit 2c23cdd4ead22c48b6016961fc1db17a78a990d6. Revert "Revert Edge Gesture [2/2]" This reverts commit 38390d48284340ed8df0c2073c0f7848a3bc8751. Change-Id: I5dfdd7bee7a2f389bcb275b3d2a135012b170e02 --- Android.mk | 3 + core/java/android/provider/Settings.java | 54 ++ .../service/gesture/EdgeGestureManager.java | 213 +++++++ .../IEdgeGestureActivationListener.aidl | 14 + .../gesture/IEdgeGestureHostCallback.aidl | 20 + .../service/gesture/IEdgeGestureService.aidl | 20 + .../java/android/service/gesture/package.html | 5 + core/java/android/view/InputDevice.java | 9 + core/java/android/view/ViewRootImpl.java | 10 + .../android/view/WindowManagerPolicy.java | 1 + .../util/gesture/EdgeGesturePosition.java | 42 ++ .../util/gesture/EdgeServiceConstants.java | 82 +++ core/res/res/values/aos_config.xml | 6 + core/res/res/values/aos_dimens.xml | 29 + .../SystemUI/res/drawable/ic_back_arrow.xml | 19 + .../res/drawable/ic_back_arrow_black.xml | 18 + .../res/values/edge_gestures_attrs.xml | 7 + .../systemui/statusbar/phone/StatusBar.java | 38 ++ .../screen_gestures/BackArrowView.java | 211 +++++++ .../ScreenGesturesController.java | 177 ++++++ .../screen_gestures/ScreenGesturesView.java | 318 +++++++++++ .../server/policy/PhoneWindowManager.java | 89 +++ .../java/com/android/server/SystemServer.java | 17 +- .../gesture/EdgeGestureInputFilter.java | 539 ++++++++++++++++++ .../server/gesture/EdgeGestureService.java | 486 ++++++++++++++++ .../server/gesture/EdgeGestureTracker.java | 252 ++++++++ 26 files changed, 2678 insertions(+), 1 deletion(-) create mode 100644 core/java/android/service/gesture/EdgeGestureManager.java create mode 100644 core/java/android/service/gesture/IEdgeGestureActivationListener.aidl create mode 100644 core/java/android/service/gesture/IEdgeGestureHostCallback.aidl create mode 100644 core/java/android/service/gesture/IEdgeGestureService.aidl create mode 100644 core/java/android/service/gesture/package.html create mode 100644 core/java/com/android/internal/util/gesture/EdgeGesturePosition.java create mode 100644 core/java/com/android/internal/util/gesture/EdgeServiceConstants.java create mode 100644 core/res/res/values/aos_dimens.xml create mode 100644 packages/SystemUI/res/drawable/ic_back_arrow.xml create mode 100644 packages/SystemUI/res/drawable/ic_back_arrow_black.xml create mode 100644 packages/SystemUI/res/values/edge_gestures_attrs.xml create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/BackArrowView.java create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesController.java create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesView.java create mode 100644 services/java/com/android/server/gesture/EdgeGestureInputFilter.java create mode 100644 services/java/com/android/server/gesture/EdgeGestureService.java create mode 100644 services/java/com/android/server/gesture/EdgeGestureTracker.java diff --git a/Android.mk b/Android.mk index cf82b96f6419e..2a26498d30cb0 100644 --- a/Android.mk +++ b/Android.mk @@ -342,6 +342,9 @@ LOCAL_SRC_FILES += \ core/java/android/service/voice/IVoiceInteractionSession.aidl \ core/java/android/service/voice/IVoiceInteractionSessionService.aidl \ core/java/android/service/gesture/IGestureService.aidl \ + core/java/android/service/gesture/IEdgeGestureService.aidl \ + core/java/android/service/gesture/IEdgeGestureActivationListener.aidl \ + core/java/android/service/gesture/IEdgeGestureHostCallback.aidl \ core/java/android/service/wallpaper/IWallpaperConnection.aidl \ core/java/android/service/wallpaper/IWallpaperEngine.aidl \ core/java/android/service/wallpaper/IWallpaperService.aidl \ diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e250c5e206866..fac4a1bd3fbb1 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4828,6 +4828,12 @@ public boolean validate(String value) { */ public static final String VOLUME_LINK_NOTIFICATION = "volume_link_notification"; + /** + * Use EdgeGesture Service for system gestures in PhoneWindowManager + * @hide + */ + public static final String USE_EDGE_SERVICE_FOR_GESTURES = "edge_service_for_gestures"; + /** * Apps to hide in the ChooserActivity * @hide @@ -8318,6 +8324,54 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val public static final String MANAGED_PROFILE_CONTACT_REMOTE_SEARCH = "managed_profile_contact_remote_search"; + /** + * Whether to use edge gestures to navigate. + * @hide + */ + public static final String EDGE_GESTURES_ENABLED = "edge_gestures_enabled"; + + /** + * Haptic feedback duration on edge gesture navigation. + * @hide + */ + public static final String EDGE_GESTURES_FEEDBACK_DURATION = "edge_gestures_feedback_duration"; + + /** + * Long press duration on edge gesture navigation. + * @hide + */ + public static final String EDGE_GESTURES_LONG_PRESS_DURATION = "edge_gestures_long_press_duration"; + + /** + * Back gesture active on this edges. + * @hide + */ + public static final String EDGE_GESTURES_BACK_EDGES = "edge_gestures_back_edges"; + + /** + * Back gesture active on this edges when on landscape. + * @hide + */ + public static final String EDGE_GESTURES_LANDSCAPE_BACK_EDGES = "edge_gestures_landscape_back_edges"; + + /** + * Activate back gestures only when Y position > than this % of screen. + * @hide + */ + public static final String EDGE_GESTURES_BACK_SCREEN_PERCENT = "edge_gestures_back_screen_percent"; + + /** + * Show UI feedback when using back gesture. + * @hide + */ + public static final String EDGE_GESTURES_BACK_SHOW_UI_FEEDBACK = "edge_gestures_back_show_ui_feedback"; + + /** + * Use black arrow theme instead of the white version. + * @hide + */ + public static final String EDGE_GESTURES_BACK_USE_BLACK_ARROW = "edge_gestures_back_use_black_arrow"; + /** * Whether or not the automatic storage manager is enabled and should run on the device. * diff --git a/core/java/android/service/gesture/EdgeGestureManager.java b/core/java/android/service/gesture/EdgeGestureManager.java new file mode 100644 index 0000000000000..18818443f9374 --- /dev/null +++ b/core/java/android/service/gesture/EdgeGestureManager.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project (Jens Doll) + * + * 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.service.gesture; + +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.service.gesture.IEdgeGestureActivationListener; +import android.service.gesture.IEdgeGestureHostCallback; +import android.service.gesture.IEdgeGestureService; +import android.util.Slog; + +import com.android.internal.util.gesture.EdgeGesturePosition; + +/** + * This is a simple Manager class for edge gesture service on the application side. The application need + * {@code INJECT_EVENTS} permission to register {@code EdgeGestureActivationListener}s.
    + * See {@link android.service.gesture.IEdgeGestureService} for more information. + * + * @see android.service.gesture.IEdgeGestureService + * @hide + */ +public class EdgeGestureManager { + public static final String TAG = "EdgeGestureManager"; + public static final boolean DEBUG = false; + + private static EdgeGestureManager sInstance; + + private final IEdgeGestureService mPs; + + public static abstract class EdgeGestureActivationListener { + private Handler mHandler; + private IEdgeGestureHostCallback mCallback; + + private class Delegator extends IEdgeGestureActivationListener.Stub { + public void onEdgeGestureActivation(final int touchX, final int touchY, final int positionIndex, final int flags) + throws RemoteException { + mHandler.post(new Runnable() { + public void run() { + EdgeGestureActivationListener.this.onEdgeGestureActivation(touchX, touchY, EdgeGesturePosition.values()[positionIndex], flags); + } + }); + } + } + private Delegator mDelegator; + + public EdgeGestureActivationListener() { + this(Looper.getMainLooper()); + } + + public EdgeGestureActivationListener(Looper looper) { + mHandler = new Handler(looper); + mDelegator = new Delegator(); + } + + /* package */ void setHostCallback(IEdgeGestureHostCallback hostCallback) { + mCallback = hostCallback; + } + + /** + * Override this to receive activations from the edge gesture service. + * + * @param touchX the last X position a touch event was registered. + * @param touchY the last Y position a touch event was registered. + * @param position the position of the activation. + * @param flags currently 0. + * @see IEdgeGestureActivationListener#onEdgeGestureActivation(int, int, int, int) + */ + public abstract void onEdgeGestureActivation(int touchX, int touchY, EdgeGesturePosition position, int flags); + + /** + * After being activated, this allows the edge gesture control to steal focus from the current + * window. + * + * @see IEdgeGestureHostCallback#gainTouchFocus(IBinder) + */ + public boolean gainTouchFocus(IBinder applicationWindowToken) { + try { + return mCallback.gainTouchFocus(applicationWindowToken); + } catch (RemoteException e) { + Slog.w(TAG, "gainTouchFocus failed: " + e.getMessage()); + /* fall through */ + } + return false; + } + + public boolean dropEventsUntilLift() { + try { + return mCallback.dropEventsUntilLift(); + } catch (RemoteException e) { + Slog.w(TAG, "dropNextEvents failed: " + e.getMessage()); + /* fall through */ + } + return false; + } + + /** + * Turns listening for edge gesture activation gestures on again, after it was disabled during + * the call to the listener. + * + * @see IEdgeGestureHostCallback#restoreListenerState() + */ + public void restoreListenerState() { + if (DEBUG) { + Slog.d(TAG, "restore listener state: " + Thread.currentThread().getName()); + } + try { + mCallback.restoreListenerState(); + } catch (RemoteException e) { + Slog.w(TAG, "restoreListenerState failed: " + e.getMessage()); + /* fall through */ + } + } + } + + private EdgeGestureManager(IEdgeGestureService ps) { + mPs = ps; + } + + /** + * Gets an instance of the edge gesture manager. + * + * @return The edge gesture manager instance. + * @hide + */ + public static EdgeGestureManager getInstance() { + synchronized (EdgeGestureManager.class) { + if (sInstance == null) { + IBinder b = ServiceManager.getService("edgegestureservice"); + sInstance = new EdgeGestureManager(IEdgeGestureService.Stub.asInterface(b)); + } + return sInstance; + } + } + + /** + * Checks if the edge gesture service is present. + *

    + * Since the service is only started at boot time and is bound to the system server, this + * is constant for the devices up time. + * + * @return {@code true} when the edge gesture service is running on this device. + * @hide + */ + public boolean isPresent() { + return mPs != null; + } + + /** + * Register a listener for edge gesture activation gestures. Initially the listener + * is set to listen for no position. Use updateedge gestureActivationListener() to + * bind the listener to positions. + * + * @param listener is the activation listener. + * @return {@code true} if the registration was successful. + * @hide + */ + public boolean setEdgeGestureActivationListener(EdgeGestureActivationListener listener) { + if (DEBUG) { + Slog.d(TAG, "Set edge gesture activation listener"); + } + if (mPs == null) { + Slog.e(TAG, "Failed to set edge gesture activation listener: Service not present"); + return false; + } + try { + IEdgeGestureHostCallback callback = mPs.registerEdgeGestureActivationListener(listener.mDelegator); + listener.setHostCallback(callback); + return true; + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set edge gesture activation listener: " + e.getMessage()); + return false; + } + } + + /** + * Update the listener to react on gestures in the given positions. + * + * @param listener is a already registered listener. + * @param positions is a bit mask describing the positions to listen to. + * @hide + */ + public void updateEdgeGestureActivationListener(EdgeGestureActivationListener listener, int positions) { + if (DEBUG) { + Slog.d(TAG, "Update edge gesture activation listener: 0x" + Integer.toHexString(positions)); + } + if (mPs == null) { + Slog.e(TAG, "Failed to update edge gesture activation listener: Service not present"); + return; + } + try { + mPs.updateEdgeGestureActivationListener(listener.mDelegator.asBinder(), positions); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to update edge gesture activation listener: " + e.getMessage()); + } + } + +} diff --git a/core/java/android/service/gesture/IEdgeGestureActivationListener.aidl b/core/java/android/service/gesture/IEdgeGestureActivationListener.aidl new file mode 100644 index 0000000000000..0c9b24a8ae3d1 --- /dev/null +++ b/core/java/android/service/gesture/IEdgeGestureActivationListener.aidl @@ -0,0 +1,14 @@ + +package android.service.gesture; + +import android.view.InputEvent; + +/** @hide */ +interface IEdgeGestureActivationListener { + + /** Called when a gesture is detected that fits to the activation gesture. At this point in + * time gesture detection is disabled. Call IEdgeGestureHostCallback.restoreState() to + * recover from this. + */ + oneway void onEdgeGestureActivation(int touchX, int touchY, int positionIndex, int flags); +} \ No newline at end of file diff --git a/core/java/android/service/gesture/IEdgeGestureHostCallback.aidl b/core/java/android/service/gesture/IEdgeGestureHostCallback.aidl new file mode 100644 index 0000000000000..c2615503e4fc3 --- /dev/null +++ b/core/java/android/service/gesture/IEdgeGestureHostCallback.aidl @@ -0,0 +1,20 @@ +package android.service.gesture; + +/** @hide */ +interface IEdgeGestureHostCallback { + + /** After being activated, this allows to steal focus from the current + * window + */ + boolean gainTouchFocus(IBinder windowToken); + + /** Turns listening for activation gestures on again, after it was disabled during + * the call to the listener. + */ + oneway void restoreListenerState(); + + /* + * Tells filter to drop all events till touch up + */ + boolean dropEventsUntilLift(); +} \ No newline at end of file diff --git a/core/java/android/service/gesture/IEdgeGestureService.aidl b/core/java/android/service/gesture/IEdgeGestureService.aidl new file mode 100644 index 0000000000000..342cf71aa32bf --- /dev/null +++ b/core/java/android/service/gesture/IEdgeGestureService.aidl @@ -0,0 +1,20 @@ +package android.service.gesture; + +import android.service.gesture.IEdgeGestureActivationListener; +import android.service.gesture.IEdgeGestureHostCallback; + +/** @hide */ +interface IEdgeGestureService { + + /** Register a listener for activation gestures. Initially the listener + * is set to listen for no position. Use updateEdgeGestureActivationListener() to + * bind the listener to positions. + * Use the returned IEdgeGestureHostCallback to manipulate the state after activation. + */ + IEdgeGestureHostCallback registerEdgeGestureActivationListener(in IEdgeGestureActivationListener listener); + + /** Update the listener to react on gestures in the given positions. + */ + void updateEdgeGestureActivationListener(in IBinder listener, int positionFlags); + +} \ No newline at end of file diff --git a/core/java/android/service/gesture/package.html b/core/java/android/service/gesture/package.html new file mode 100644 index 0000000000000..c9f96a66ab3bc --- /dev/null +++ b/core/java/android/service/gesture/package.html @@ -0,0 +1,5 @@ + + +{@hide} + + diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index a8b07c98a25c6..6e1978b78b181 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -301,6 +301,15 @@ public final class InputDevice implements Parcelable { */ public static final int SOURCE_HDMI = 0x02000000 | SOURCE_CLASS_BUTTON; + /** + * The input source is a custom virtual key event sent programmatically to emulate different events. + * + * The key requested is different from the actual key's event. + * + * @hide + */ + public static final int SOURCE_CUSTOM = 0x08000000 | SOURCE_CLASS_BUTTON; + /** * The input source is a specific virtual event sent from navigation bar. * diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3f1ea34c37ab0..76fcb8170603b 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -474,6 +474,8 @@ static final class SystemUiVisibilityInfo { } private String mTag = TAG; + boolean mHaveMoveEvent = false; + boolean mIsPerfLockAcquired = false; public ViewRootImpl(Context context, Display display) { mContext = context; @@ -4793,6 +4795,14 @@ private int processPointerEvent(QueuedInputEvent q) { mAttachInfo.mUnbufferedDispatchRequested = false; mAttachInfo.mHandlingPointerEvent = true; boolean handled = mView.dispatchPointerEvent(event); + + int action = event.getActionMasked(); + if (action == MotionEvent.ACTION_MOVE) { + mHaveMoveEvent = true; + } else if (action == MotionEvent.ACTION_UP) { + mHaveMoveEvent = false; + mIsPerfLockAcquired = false; + } maybeUpdatePointerIcon(event); maybeUpdateTooltip(event); mAttachInfo.mHandlingPointerEvent = false; diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 69f90f848e0ac..616cf436a9cb4 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -1753,4 +1753,5 @@ public void getNonDecorInsetsLw(int displayRotation, int displayWidth, int displ * @return true if ready; false otherwise. */ boolean canDismissBootAnimation(); + } diff --git a/core/java/com/android/internal/util/gesture/EdgeGesturePosition.java b/core/java/com/android/internal/util/gesture/EdgeGesturePosition.java new file mode 100644 index 0000000000000..01cfdeae54fbe --- /dev/null +++ b/core/java/com/android/internal/util/gesture/EdgeGesturePosition.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project (Jens Doll) + * + * 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.internal.util.gesture; + +/** + * Defines the positions in which gestures may be recognized by the + * edge gesture service. + * This defines an index and an flag for each position. + */ +public enum EdgeGesturePosition { + LEFT(0, 0), + BOTTOM(1, 1), + RIGHT(2, 1), + TOP(3, 0); + + EdgeGesturePosition(int index, int factor) { + INDEX = index; + FLAG = (0x01< + * Positions are specified by {@code EdgeGesturePosition.FLAG}. + */ + public static final int POSITION_MASK = 0x0000001f; + + /** + * Mask for coding sensitivity within the flags of + * {@code updateEdgeGestureActivationListener()}. + *

    + * Sensitivity influences the speed of the swipe, the trigger area, and trigger distance that + * is needed to activate the edge gesture. + */ + public static final int SENSITIVITY_MASK = 0x70000000; + + /** + * Number of bits to shift left, to get a integer within the {@link #SENSITIVITY_MASK}. + */ + public static final int SENSITIVITY_SHIFT = 28; + + /** + * No sensitivity specified at all, the service may choose a sensitivity level on its own. + */ + public static final int SENSITIVITY_NONE = 0; + + /** + * Default sensitivity, picked by the edge gesture service automatically. + */ + public static final int SENSITIVITY_DEFAULT = 2; + + /** + * Lowest valid sensitivity value. + */ + public static final int SENSITIVITY_LOWEST = 1; + + /** + * Highest sensitivity value. + */ + public static final int SENSITIVITY_HIGHEST = 4; + + /** + * Do not cut 10% area on th edges + */ + public static final int UNRESTRICTED = 0x10; + + /** + * This listener does not likes enabling/disabling filter + * because it interrupt in motion events. + */ + public static final int LONG_LIVING = 0x20; + +} diff --git a/core/res/res/values/aos_config.xml b/core/res/res/values/aos_config.xml index c370c3f4bdff9..506007808fca0 100644 --- a/core/res/res/values/aos_config.xml +++ b/core/res/res/values/aos_config.xml @@ -138,4 +138,10 @@ false + + + + + + diff --git a/core/res/res/values/aos_dimens.xml b/core/res/res/values/aos_dimens.xml new file mode 100644 index 0000000000000..9f18287d90e09 --- /dev/null +++ b/core/res/res/values/aos_dimens.xml @@ -0,0 +1,29 @@ + + + + + + 12dp + + + 15dp + + + 12dp + + diff --git a/packages/SystemUI/res/drawable/ic_back_arrow.xml b/packages/SystemUI/res/drawable/ic_back_arrow.xml new file mode 100644 index 0000000000000..9bfb6b0568a8f --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_back_arrow.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/packages/SystemUI/res/drawable/ic_back_arrow_black.xml b/packages/SystemUI/res/drawable/ic_back_arrow_black.xml new file mode 100644 index 0000000000000..ff4de4ad12b94 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_back_arrow_black.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/packages/SystemUI/res/values/edge_gestures_attrs.xml b/packages/SystemUI/res/values/edge_gestures_attrs.xml new file mode 100644 index 0000000000000..9ff174487a010 --- /dev/null +++ b/packages/SystemUI/res/values/edge_gestures_attrs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 8d5481228a033..a3dc900ecfd49 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -271,6 +271,7 @@ import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.statusbar.screen_gestures.ScreenGesturesController; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout .OnChildLocationsChangedListener; @@ -541,6 +542,9 @@ public class StatusBar extends SystemUI implements DemoMode, private int mAmbientMediaPlaying; + // Full Screen Gestures + protected ScreenGesturesController gesturesController; + // Tracking finger for opening/closing. boolean mTracking; @@ -1125,6 +1129,10 @@ public void start() { com.android.internal.R.array.config_nonBlockableNotificationPackages)); // end old BaseStatusBar.start(). + mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.EDGE_GESTURES_ENABLED), false, + mEdgeGesturesSettingsObserver); + mMediaSessionManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); // TODO: use MediaSessionManager.SessionListener to hook us up to future updates @@ -1522,6 +1530,12 @@ public void onDensityOrFontScaleChanged() { mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext); reevaluateStyles(); + + ContentResolver resolver = mContext.getContentResolver(); + + boolean edgeGesturesEnabled = Settings.Secure.getIntForUser(resolver, + Settings.Secure.EDGE_GESTURES_ENABLED, 0, UserHandle.USER_CURRENT) == 1; + updateEdgeGestures(edgeGesturesEnabled); } @Override @@ -6530,6 +6544,17 @@ public boolean isDeviceInteractive() { return mDeviceInteractive; } + private final ContentObserver mEdgeGesturesSettingsObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + ContentResolver resolver = mContext.getContentResolver(); + boolean edgeGesturesEnabled = Settings.Secure.getIntForUser(resolver, + Settings.Secure.EDGE_GESTURES_ENABLED, 0, UserHandle.USER_CURRENT) == 1; + + updateEdgeGestures(edgeGesturesEnabled); + } + }; + @Override // NotificationData.Environment public boolean isDeviceProvisioned() { return mDeviceProvisionedController.isDeviceProvisioned(); @@ -8716,4 +8741,17 @@ public void startAssist(Bundle args) { mNavigationBar.getBarTransitions().setAutoDim(true); } }; + + public void updateEdgeGestures(boolean enabled) { + Log.d(TAG, "updateEdgeGestures: Updating edge gestures"); + if (enabled) { + if (gesturesController == null) { + gesturesController = new ScreenGesturesController(mContext, mWindowManager, this); + } + gesturesController.reorient(); + } else if (!enabled && gesturesController != null) { + gesturesController.stop(); + gesturesController = null; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/BackArrowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/BackArrowView.java new file mode 100644 index 0000000000000..0046adf827a77 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/BackArrowView.java @@ -0,0 +1,211 @@ +package com.android.systemui.statusbar.screen_gestures; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.graphics.drawable.VectorDrawableCompat; +import android.util.AttributeSet; +import com.android.systemui.R; + +import android.util.Log; +import android.view.View; + +public class BackArrowView extends View { + + private static final String TAG = "BackArrowView"; + + private int posX = -1; + private int posY = -1; + + private int vectorSize = 0; + private int contentsPaddingLeft = 0; + private int contentsPaddingTop = 0; + + private Path topArch; + private Path bottomArch; + private Rect backgroundRect; + + private Paint eraser; + private Paint painter; + + private Drawable arrowDrawable; + + private boolean isReversed = false; + private boolean useBlackArrow = false; + + private boolean animating = false; + + public String name = ""; + + public BackArrowView(Context context) { + this(context, null); + } + + public BackArrowView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BackArrowView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public BackArrowView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.BackArrowView, defStyleAttr, defStyleRes); + isReversed = typedArray.getBoolean(R.styleable.BackArrowView_reversed, false); + useBlackArrow = typedArray.getBoolean(R.styleable.BackArrowView_black_arrow, false); + typedArray.recycle(); + + init(); + } + + private void init() { + setBackgroundColor(Color.TRANSPARENT); + setLayerType(LAYER_TYPE_HARDWARE, null); + + eraser = new Paint(); + eraser.setStyle(Paint.Style.FILL); + eraser.setAntiAlias(true); + eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + + painter = new Paint(); + painter.setColor(Color.BLACK); + painter.setAntiAlias(true); + painter.setStyle(Paint.Style.FILL); + + setUseBlackArrow(useBlackArrow); + + contentsPaddingLeft = vectorSize; + contentsPaddingTop = vectorSize / 2; + + topArch = new Path(); + bottomArch = new Path(); + backgroundRect = new Rect(); + } + + @Override + protected void onDraw(Canvas canvas) { + if (isReversed) canvas.scale(-1, 1, getWidth()/2, getHeight()/2); + super.onDraw(canvas); + + canvas.drawColor(Color.TRANSPARENT); + + if (posX < (-getWidth() + contentsPaddingLeft) || posY < 0) return; + + canvas.drawRect(backgroundRect, painter); + canvas.drawPath(topArch, eraser); + canvas.drawPath(bottomArch, eraser); + + arrowDrawable.draw(canvas); + } + + public void onTouchStarted(int posX, int posY) { + Log.d(TAG, name + " onTouchStarted: "); + animating = true; + + reset(); + + this.posX = posX; + this.posY = posY; + } + + public void onTouchMoved(int x, int y) { + if (!animating) return; + + this.posX = x; + this.posY = y; + + // Reverse the X position, to make the 'mirrored' view work + if (isReversed) this.posX = -posX + getWidth(); + + if (posX < (-getWidth() + contentsPaddingLeft)) posX = -getWidth() + contentsPaddingLeft; + if (posY < contentsPaddingTop) posY = contentsPaddingTop; + + Log.d(TAG, name + " onTouchMoved: X: " + this.posX +", Y: "+this.posY); + + topArch = new Path(); + topArch.moveTo(posX, posY); + topArch.addArc(posX - getWidth(), + -1, // Needed to hide small line at the top + getWidth(), + posY, + 0, + 180); + + bottomArch = new Path(); + bottomArch.moveTo(posX, posY); + + bottomArch.addArc(posX - getWidth(), + posY - 10, + getWidth(), + getHeight() + posY, + 180, + 270); + + backgroundRect = new Rect( (10 + posX + getWidth() - contentsPaddingLeft) / 2, + posY / 2, + getWidth(), + posY + getHeight()/2); + + int arrowStartX = posX; + if (posX < 0) arrowStartX = 0; // Don't show the arrow icon outside the drawing rect + + int leftArrowPos = arrowStartX; // Offset the arrow to center it on X position + arrowDrawable.setBounds(leftArrowPos, + posY - vectorSize / 2, + leftArrowPos + vectorSize, + posY + vectorSize / 2); + + invalidate(); + } + + public void onTouchEnded() { + Log.d(TAG, name + "onTouchEnded: "); + animating = false; + + reset(); + } + + private void reset() { + this.posX = -1; + this.posY = -1; + + topArch = new Path(); + bottomArch = new Path(); + backgroundRect = new Rect(); + arrowDrawable.setBounds(0, 0, 0, 0); + + invalidate(); + } + + public boolean isReversed() { + return isReversed; + } + + public void setReversed(boolean reversed) { + isReversed = reversed; + } + + public boolean usesBlackArrow() { + return useBlackArrow; + } + + public void setUseBlackArrow(boolean useBlackArrow) { + this.useBlackArrow = useBlackArrow; + + float density = Resources.getSystem().getDisplayMetrics().density; + int iconId = useBlackArrow ? R.drawable.ic_back_arrow_black : R.drawable.ic_back_arrow; + arrowDrawable = VectorDrawableCompat.create(getResources(), iconId, getContext().getTheme()); + vectorSize = (int) (32f * density); + arrowDrawable.setBounds(0, 0, vectorSize, vectorSize); + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesController.java b/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesController.java new file mode 100644 index 0000000000000..f66002f3e3bf9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesController.java @@ -0,0 +1,177 @@ +package com.android.systemui.statusbar.screen_gestures; + +import android.app.KeyguardManager; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.hardware.input.InputManager; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; +import android.os.UserHandle; +import android.provider.Settings; +import android.service.gesture.EdgeGestureManager; +import android.util.Log; +import android.view.InputDevice; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.ViewGroup; +import android.view.WindowManager; + +import com.android.internal.util.gesture.EdgeGesturePosition; +import com.android.internal.util.gesture.EdgeServiceConstants; +import com.android.systemui.statusbar.phone.StatusBar; + +/** + * Created by arasthel on 15/02/18. + */ + +public class ScreenGesturesController { + + public static final boolean DEBUG = false; + + private static final String TAG = "ScreenGesturesControlle"; + + private Context context; + private WindowManager windowManager; + private StatusBar statusBar; + private KeyguardManager keyguardManager; + + private ScreenGesturesView screenGesturesView; + + private EdgeGestureManager edgeGestureManager = EdgeGestureManager.getInstance(); + + private EdgeGestureManager.EdgeGestureActivationListener gestureManagerListener = new EdgeGestureManager.EdgeGestureActivationListener() { + + @Override + public void onEdgeGestureActivation(int touchX, int touchY, EdgeGesturePosition position, int flags) { + if (DEBUG) Log.d(TAG, "onEdgeGestureActivation: Starting gesture"); + final ScreenGesturesView gesturesView = screenGesturesView; + + if (gesturesView != null && !keyguardManager.isKeyguardLocked()) { + boolean startGesture = true; + String backSettingsId = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? + Settings.Secure.EDGE_GESTURES_BACK_EDGES : + Settings.Secure.EDGE_GESTURES_LANDSCAPE_BACK_EDGES; + int backGestureEdgesFlag = 0; + int percent = 0; + try { + backGestureEdgesFlag = Settings.Secure.getIntForUser(context.getContentResolver(), + backSettingsId, + UserHandle.USER_CURRENT); + percent = Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.EDGE_GESTURES_BACK_SCREEN_PERCENT, + UserHandle.USER_CURRENT); + } catch (Settings.SettingNotFoundException e) { + e.printStackTrace(); + } + if ((position.FLAG & backGestureEdgesFlag) != 0) { + Point displaySize = new Point(); + windowManager.getDefaultDisplay().getSize(displaySize); + startGesture = touchY > (percent*displaySize.y)/100; + } + + if (startGesture) { + gesturesView.startGesture(touchX, touchY, position); + + handler.post(() -> { + gainTouchFocus(gesturesView.getWindowToken()); + gestureManagerListener.restoreListenerState(); + }); + } else { + gestureManagerListener.restoreListenerState(); + } + } else { + gestureManagerListener.restoreListenerState(); + } + + } + }; + + private ScreenGesturesView.OnGestureCompletedListener onGestureCompletedListener = gestureType -> { + switch (gestureType) { + case ScreenGesturesView.GestureType.HOME: + injectKeyCode(KeyEvent.KEYCODE_HOME); + break; + case ScreenGesturesView.GestureType.BACK: + injectKeyCode(KeyEvent.KEYCODE_BACK); + break; + case ScreenGesturesView.GestureType.RECENTS: + injectKeyCode(KeyEvent.KEYCODE_APP_SWITCH); + break; + default: + Log.e(TAG, "Unknown event"); + break; + } + + gestureManagerListener.restoreListenerState(); + + //setupEdgeGestureManager(); + }; + + private Handler handler = new Handler(Looper.getMainLooper()); + + public ScreenGesturesController(Context context, WindowManager windowManager, StatusBar statusBar) { + this.context = context; + this.windowManager = windowManager; + this.statusBar = statusBar; + + keyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + + edgeGestureManager.setEdgeGestureActivationListener(gestureManagerListener); + setupEdgeGestureManager(); + + screenGesturesView = new ScreenGesturesView(context); + WindowManager.LayoutParams params = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, + PixelFormat.TRANSLUCENT); + windowManager.addView(screenGesturesView, params); + + screenGesturesView.setOnGestureCompletedListener(onGestureCompletedListener); + } + + public void reorient() { + setupEdgeGestureManager(); + } + + public void stop() { + gestureManagerListener.restoreListenerState(); + edgeGestureManager.updateEdgeGestureActivationListener(gestureManagerListener, 0); + windowManager.removeView(screenGesturesView); + screenGesturesView = null; + } + + private void setupEdgeGestureManager() { + int sensitivity = EdgeServiceConstants.SENSITIVITY_HIGHEST; + + int edges = (EdgeGesturePosition.BOTTOM.FLAG | EdgeGesturePosition.RIGHT.FLAG | EdgeGesturePosition.LEFT.FLAG); + edgeGestureManager.updateEdgeGestureActivationListener(gestureManagerListener, + sensitivity << EdgeServiceConstants.SENSITIVITY_SHIFT + | edges + | EdgeServiceConstants.LONG_LIVING + | EdgeServiceConstants.UNRESTRICTED); + } + + private void injectKeyCode(int keyCode) { + if (DEBUG) Log.d(TAG, "injectKeyCode: Injecting: " + String.valueOf(keyCode)); + + InputManager inputManager = InputManager.getInstance(); + long now = SystemClock.uptimeMillis(); + + KeyEvent downEvent = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0, + KeyCharacterMap.VIRTUAL_KEYBOARD, 0, + KeyEvent.FLAG_FROM_SYSTEM, InputDevice.SOURCE_CUSTOM); + + KeyEvent upEvent = KeyEvent.changeAction(downEvent, KeyEvent.ACTION_UP); + + inputManager.injectInputEvent(downEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + inputManager.injectInputEvent(upEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesView.java b/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesView.java new file mode 100644 index 0000000000000..3c579ebbe6c7a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/screen_gestures/ScreenGesturesView.java @@ -0,0 +1,318 @@ +package com.android.systemui.statusbar.screen_gestures; + +import android.content.Context; +import android.content.res.Configuration; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.os.Vibrator; +import android.provider.Settings; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import com.android.internal.util.gesture.EdgeGesturePosition; + +/** + * Created by arasthel on 15/02/18. + */ + +public class ScreenGesturesView extends FrameLayout { + + public static final boolean DEBUG = false; + + public static final int SIGNIFICANT_MOVE = 10; + + private static final String TAG = "ScreenGesturesView"; + + public static class GestureType { + public static final int NONE = 0; + public static final int HOME = 1; + public static final int BACK = 1 << 1; + public static final int RECENTS = 1 << 2; + } + + private OnGestureCompletedListener onGestureCompletedListener; + + private int initialX = -1; + private int initialY = -1; + + private int lastX = -1; + private int lastY = -1; + + private int possibleGestures = GestureType.NONE; + + private Vibrator vibrator; + + private Handler handler = new Handler(Looper.getMainLooper()); + + private BackArrowView leftArrowView; + private BackArrowView rightArrowView; + + private ContentObserver blackThemeContentObserver = new ContentObserver(handler) { + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + boolean useBlackTheme = uiFeedbackUsesBlackTheme(); + leftArrowView.setUseBlackArrow(useBlackTheme); + rightArrowView.setUseBlackArrow(useBlackTheme); + } + + @Override + public boolean deliverSelfNotifications() { + return true; + } + }; + + public ScreenGesturesView(Context context) { + this(context, null); + } + + public ScreenGesturesView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ScreenGesturesView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ScreenGesturesView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + boolean useBlackThemeForUI = uiFeedbackUsesBlackTheme(); + + leftArrowView = new BackArrowView(context); + leftArrowView.setReversed(true); + leftArrowView.setUseBlackArrow(useBlackThemeForUI); + leftArrowView.name = "LEFT"; + + rightArrowView = new BackArrowView(context); + rightArrowView.setUseBlackArrow(useBlackThemeForUI); + rightArrowView.name = "RIGHT"; + + addView(leftArrowView); + addView(rightArrowView); + + final float density = getResources().getDisplayMetrics().density; + FrameLayout.LayoutParams leftParams = new FrameLayout.LayoutParams((int) (48 * density), ViewGroup.LayoutParams.MATCH_PARENT); + FrameLayout.LayoutParams rightParams = new FrameLayout.LayoutParams((int) (48 * density), ViewGroup.LayoutParams.MATCH_PARENT); + rightParams.gravity = Gravity.RIGHT; + rightParams.rightMargin = 0; + leftArrowView.setLayoutParams(leftParams); + rightArrowView.setLayoutParams(rightParams); + + Uri blackThemeUri = Settings.Secure.getUriFor(Settings.Secure.EDGE_GESTURES_BACK_USE_BLACK_ARROW); + context.getContentResolver().registerContentObserver(blackThemeUri, false, blackThemeContentObserver); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + setVisibility(View.GONE); + + vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); + } + + public void startGesture(int initialX, int initialY, EdgeGesturePosition position) { + if (DEBUG) Log.d(TAG, "startGesture: Gesture started"); + + this.initialX = initialX; + this.initialY = initialY; + + this.lastX = initialX; + this.lastY = initialY; + + String backSettingsId = getContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? + Settings.Secure.EDGE_GESTURES_BACK_EDGES : + Settings.Secure.EDGE_GESTURES_LANDSCAPE_BACK_EDGES; + int backGestureEdgesFlag = Settings.Secure.getIntForUser(getContext().getContentResolver(), + backSettingsId, + 0, + UserHandle.USER_CURRENT); + + setVisibility(View.VISIBLE); + + if ((position.FLAG & backGestureEdgesFlag) != 0) { + possibleGestures = GestureType.BACK; + + if (shouldShowUIFeedback()) { + if (initialX < getWidth() / 2) { + leftArrowView.onTouchStarted(initialX - leftArrowView.getLeft(), initialY - leftArrowView.getTop()); + } else { + rightArrowView.onTouchStarted(initialX - rightArrowView.getLeft(), initialY - rightArrowView.getTop()); + } + } + } else if ((position.FLAG & EdgeGesturePosition.BOTTOM.FLAG) != 0) { + possibleGestures = GestureType.HOME | GestureType.RECENTS; + } else { + if (onGestureCompletedListener != null) { + onGestureCompletedListener.onGestureCompleted(GestureType.NONE); + } + return; + } + } + + private int getFeedbackStrength() { + try { + return Settings.Secure.getInt(getContext().getContentResolver(), Settings.Secure.EDGE_GESTURES_FEEDBACK_DURATION); + } catch (Settings.SettingNotFoundException exception) { + return 100; + } + } + + private int getLongpressDuration() { + try { + return Settings.Secure.getInt(getContext().getContentResolver(), Settings.Secure.EDGE_GESTURES_LONG_PRESS_DURATION); + } catch (Settings.SettingNotFoundException exception) { + return 500; + } + } + + private boolean shouldShowUIFeedback() { + try { + return Settings.Secure.getInt(getContext().getContentResolver(), Settings.Secure.EDGE_GESTURES_BACK_SHOW_UI_FEEDBACK) == 1; + } catch (Settings.SettingNotFoundException exception) { + return true; + } + } + + private boolean uiFeedbackUsesBlackTheme() { + try { + return Settings.Secure.getInt(getContext().getContentResolver(), Settings.Secure.EDGE_GESTURES_BACK_USE_BLACK_ARROW) == 1; + } catch (Settings.SettingNotFoundException exception) { + return true; + } + } + + private void stopGesture(int posX, int posY) { + if (DEBUG) Log.d(TAG, "stopGesture: Gesture stopped"); + + stopLongPress(); + + if (onGestureCompletedListener == null) return; + + if (DEBUG) Log.d(TAG, "stopGesture: Initial x: " + String.valueOf(initialX) + ", final x: " + String.valueOf(posX)); + if (DEBUG) Log.d(TAG, "stopGesture: Initial y: " + String.valueOf(initialY) + ", final y: " + String.valueOf(posY)); + + final int threshold = 20; + boolean canSendHome = (possibleGestures & GestureType.HOME) != 0; + if (canSendHome && (posY - initialY < -threshold)) { + if (DEBUG) Log.d(TAG, "stopGesture: Home"); + vibrator.vibrate(getFeedbackStrength()); + onGestureCompletedListener.onGestureCompleted(GestureType.HOME); + return; + } + + boolean canSendBack = (possibleGestures & GestureType.BACK) != 0; + if (canSendBack && (Math.abs(posX - initialX) > threshold)) { + if (DEBUG) Log.d(TAG, "stopGesture: Back"); + vibrator.vibrate(getFeedbackStrength()); + onGestureCompletedListener.onGestureCompleted(GestureType.BACK); + return; + } + + if (DEBUG) Log.d(TAG, "stopGesture: None"); + onGestureCompletedListener.onGestureCompleted(GestureType.NONE); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + if (DEBUG) Log.d(TAG, "onTouchEvent: DOWN"); + return true; + case MotionEvent.ACTION_MOVE: + if (DEBUG) Log.d(TAG, "onTouchEvent: MOVE"); + int x = (int) event.getX(); + int y = (int) event.getY(); + if (Math.abs(x - lastX) > SIGNIFICANT_MOVE || Math.abs(y - lastY) > SIGNIFICANT_MOVE) { + stopLongPress(); + startLongPress(); + } + + lastX = x; + lastY = y; + + if ((possibleGestures & GestureType.BACK) != 0) { + leftArrowView.onTouchMoved(x - leftArrowView.getLeft(), y - leftArrowView.getTop()); + rightArrowView.onTouchMoved(x - rightArrowView.getLeft(), y - rightArrowView.getTop()); + } + + return false; + case MotionEvent.ACTION_UP: + if (DEBUG) Log.d(TAG, "onTouchEvent: UP"); + + if (shouldShowUIFeedback()) { + leftArrowView.onTouchEnded(); + rightArrowView.onTouchEnded(); + } + + if (possibleGestures != GestureType.NONE) { + stopGesture((int) event.getX(), (int) event.getY()); + handler.postDelayed(() -> setVisibility(View.GONE), 10); + } + return true; + case MotionEvent.ACTION_CANCEL: + if (DEBUG) Log.d(TAG, "onTouchEvent: CANCEL"); + + if (shouldShowUIFeedback()) { + leftArrowView.onTouchEnded(); + rightArrowView.onTouchEnded(); + } + + stopGesture((int) event.getX(), (int) event.getY()); + handler.postDelayed(() -> setVisibility(View.GONE), 10); + + return true; + } + return false; + } + + private void startLongPress() { + if (DEBUG) Log.d(TAG, "startLongPress: scheduling long press"); + handler.postDelayed(longPressRunnable, getLongpressDuration()); + } + + private void stopLongPress() { + if (DEBUG) Log.d(TAG, "stopLongPress: cancellling long press"); + handler.removeCallbacks(longPressRunnable); + } + + private Runnable longPressRunnable = new Runnable() { + @Override + public void run() { + boolean canSendRecents = (possibleGestures & GestureType.RECENTS) != 0; + if (canSendRecents) { + possibleGestures = GestureType.NONE; + + leftArrowView.onTouchEnded(); + rightArrowView.onTouchEnded(); + + setVisibility(View.GONE); + if (onGestureCompletedListener != null) { + onGestureCompletedListener.onGestureCompleted(GestureType.RECENTS); + } + vibrator.vibrate(getFeedbackStrength()); + } + } + }; + + public OnGestureCompletedListener getOnGestureCompletedListener() { + return onGestureCompletedListener; + } + + public void setOnGestureCompletedListener(OnGestureCompletedListener onGestureCompletedListener) { + this.onGestureCompletedListener = onGestureCompletedListener; + } + + interface OnGestureCompletedListener { + void onGestureCompleted(int gestureType); + } +} diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 9ed35690dec15..7bddcdda3a072 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -205,6 +205,7 @@ import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.service.vr.IPersistentVrStateCallbacks; +import android.service.gesture.EdgeGestureManager; import android.speech.RecognizerIntent; import android.telecom.TelecomManager; import android.util.DisplayMetrics; @@ -256,6 +257,8 @@ import com.android.internal.policy.IShortcutService; import com.android.internal.policy.PhoneWindow; import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.util.gesture.EdgeGesturePosition; +import com.android.internal.util.gesture.EdgeServiceConstants; import com.android.internal.util.ScreenShapeHelper; import com.android.internal.utils.du.ActionHandler; import com.android.internal.utils.du.DUActionUtils; @@ -862,6 +865,8 @@ public void onDrawn() { // Maps global key codes to the components that will handle them. private GlobalKeyManager mGlobalKeyManager; + private boolean mGlobalActionsOnLockDisable; + // Fallback actions by key code. private final SparseArray mFallbackActions = new SparseArray(); @@ -1219,6 +1224,9 @@ void observe() { resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.HOME_WAKE_SCREEN), false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.USE_EDGE_SERVICE_FOR_GESTURES), false, this, + UserHandle.USER_ALL); resolver.registerContentObserver(Settings.Global.getUriFor( Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED), false, this, UserHandle.USER_ALL); @@ -1316,6 +1324,67 @@ public void onBarVisibilityChanged(boolean visible) { private SystemGesturesPointerEventListener mSystemGestures; + private EdgeGestureManager.EdgeGestureActivationListener mEdgeGestureActivationListener + = new EdgeGestureManager.EdgeGestureActivationListener() { + + @Override + public void onEdgeGestureActivation(int touchX, int touchY, + EdgeGesturePosition position, int flags) { + WindowState target = null; + + if (position == EdgeGesturePosition.TOP) { + target = mStatusBar; + } else if ((position == EdgeGesturePosition.BOTTOM) && (mNavigationBarPosition == NAV_BAR_BOTTOM)) { + target = mNavigationBar; + } else if ((position == EdgeGesturePosition.RIGHT) && (mNavigationBarPosition == NAV_BAR_RIGHT)) { + target = mNavigationBar; + } else if ((position == EdgeGesturePosition.LEFT) && (mNavigationBarPosition == NAV_BAR_LEFT)) { + target = mNavigationBar; + } + + if (target != null) { + requestTransientBars(target); + dropEventsUntilLift(); + mEdgeListenerActivated = true; + } else { + restoreListenerState(); + } + } + }; + private EdgeGestureManager mEdgeGestureManager = null; + private int mLastEdgePositions = 0; + private boolean mEdgeListenerActivated = false; + private boolean mUsingEdgeGestureServiceForGestures = false; + + private void updateEdgeGestureListenerState() { + int flags = 0; + if (mUsingEdgeGestureServiceForGestures) { + flags = EdgeServiceConstants.LONG_LIVING | EdgeServiceConstants.UNRESTRICTED; + if (mStatusBar != null && !mStatusBar.isVisibleLw()) { + flags |= EdgeGesturePosition.TOP.FLAG; + } + if (mNavigationBar != null && !mNavigationBar.isVisibleLw() + && !isStatusBarKeyguard()) { + if (mNavigationBarPosition == NAV_BAR_BOTTOM) { + flags |= EdgeGesturePosition.BOTTOM.FLAG; + } else if (mNavigationBarPosition == NAV_BAR_RIGHT){ + flags |= EdgeGesturePosition.RIGHT.FLAG; + } else if (mNavigationBarPosition == NAV_BAR_LEFT) { + flags |= EdgeGesturePosition.LEFT.FLAG; + } + } + } + if (mEdgeListenerActivated) { + mEdgeGestureActivationListener.restoreListenerState(); + mEdgeListenerActivated = false; + } + if (flags != mLastEdgePositions) { + mEdgeGestureManager.updateEdgeGestureActivationListener(mEdgeGestureActivationListener, + flags); + mLastEdgePositions = flags; + } + } + IStatusBarService getStatusBarService() { synchronized (mServiceAquireLock) { if (mStatusBarService == null) { @@ -2746,6 +2815,19 @@ public void updateSettings() { mUserRotationAngles = Settings.System.getInt(resolver, Settings.System.ACCELEROMETER_ROTATION_ANGLES, -1); + final boolean useEdgeService = Settings.System.getIntForUser(resolver, + Settings.System.USE_EDGE_SERVICE_FOR_GESTURES, 1, UserHandle.USER_CURRENT) == 1; + if (useEdgeService ^ mUsingEdgeGestureServiceForGestures && mSystemReady) { + if (!mUsingEdgeGestureServiceForGestures && useEdgeService) { + mUsingEdgeGestureServiceForGestures = true; + mWindowManagerFuncs.unregisterPointerEventListener(mSystemGestures); + } else if (mUsingEdgeGestureServiceForGestures && !useEdgeService) { + mUsingEdgeGestureServiceForGestures = false; + mWindowManagerFuncs.registerPointerEventListener(mSystemGestures); + } + updateEdgeGestureListenerState(); + } + if (mSystemReady) { int pointerLocation = Settings.System.getIntForUser(resolver, Settings.System.POINTER_LOCATION, 0, UserHandle.USER_CURRENT); @@ -2809,6 +2891,7 @@ public void updateSettings() { if (updateRotation) { updateRotation(true); } + } private void updateNavigationBarSize() { @@ -4798,6 +4881,8 @@ public int adjustSystemUiVisibilityLw(int visibility) { mStatusBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility); mNavigationBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility); + updateEdgeGestureListenerState(); + // Reset any bits in mForceClearingStatusBarVisibility that // are now clear. mResettingSystemUiFlags &= visibility; @@ -6187,6 +6272,7 @@ public int finishPostLayoutPolicyLw() { // update since mAllowLockscreenWhenOn might have changed updateLockScreenTimeout(); + updateEdgeGestureListenerState(); return changes; } @@ -8245,6 +8331,9 @@ public void systemReady() { if (mVrManagerInternal != null) { mVrManagerInternal.addPersistentVrModeStateListener(mPersistentVrModeListener); } + mEdgeGestureManager = EdgeGestureManager.getInstance(); + mEdgeGestureManager.setEdgeGestureActivationListener(mEdgeGestureActivationListener); + mSettingsObserver.observe(); mANBIHandler = new ANBIHandler(mContext); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 7d9517a22a924..5c1db44ee49a9 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -91,6 +91,7 @@ import com.android.server.os.SchedulingPolicyService; import com.android.server.pocket.PocketService; import com.android.server.pm.BackgroundDexOptService; +import com.android.server.gesture.EdgeGestureService; import com.android.server.pm.Installer; import com.android.server.pm.LauncherAppsService; import com.android.server.pm.OtaDexoptService; @@ -909,6 +910,7 @@ private void startOtherServices() { ILockSettings lockSettings = null; MediaRouterService mediaRouter = null; GestureService gestureService = null; + EdgeGestureService edgeGestureService = null; // Bring up services needed for UI. if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) { @@ -1540,6 +1542,13 @@ private void startOtherServices() { Slog.i(TAG, "Starting PocketService"); mSystemServiceManager.startService(PocketService.class); + try { + Slog.i(TAG, "EdgeGesture service"); + edgeGestureService = new EdgeGestureService(context, inputManager); + ServiceManager.addService("edgegestureservice", edgeGestureService); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting EdgeGesture service", e); + } } if (!disableNonCoreServices && !disableMediaProjection) { @@ -1687,8 +1696,14 @@ private void startOtherServices() { reportWtf("making Display Manager Service ready", e); } traceEnd(); - mSystemServiceManager.setSafeMode(safeMode); + if (edgeGestureService != null) { + try { + edgeGestureService.systemReady(); + } catch (Throwable e) { + reportWtf("making EdgeGesture service ready", e); + } + } if (gestureService != null) { try { diff --git a/services/java/com/android/server/gesture/EdgeGestureInputFilter.java b/services/java/com/android/server/gesture/EdgeGestureInputFilter.java new file mode 100644 index 0000000000000..c42b7d098b45d --- /dev/null +++ b/services/java/com/android/server/gesture/EdgeGestureInputFilter.java @@ -0,0 +1,539 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project (Jens Doll) + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.server.gesture; + +import android.content.Context; +import android.content.res.Resources; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.Trace; +import android.util.Slog; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.IInputFilter; +import android.view.IInputFilterHost; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.MotionEvent; +import android.view.WindowManagerPolicy; +import android.view.MotionEvent.PointerCoords; +import android.view.MotionEvent.PointerProperties; + +import com.android.internal.R; +import com.android.internal.util.gesture.EdgeGesturePosition; +import com.android.server.gesture.EdgeGestureTracker.OnActivationListener; + +import java.io.PrintWriter; + +/** + * A simple input filter, that listens for edge swipe gestures in the motion event input + * stream. + *

    + * There are 5 distinct states of this filter. + * 1) LISTEN: + * mTracker.active == false + * All motion events are passed through. If a ACTION_DOWN within a gesture trigger area happen + * switch to DETECTING. + * 2) DETECTING: + * mTracker.active == true + * All events are buffered now, and the gesture is checked by mTracker. If mTracker rejects + * the gesture (hopefully as fast as possible) all cached events will be flushed out and the + * filter falls back to LISTEN. + * If mTracker accepts the gesture, clear all cached events and go to LOCKED. + * 3) LOCKED: + * mTracker.active == false + * All events will be cached until the state changes to SYNTHESIZE through a filter + * unlock event. If there is a ACTION_UP, _CANCEL or any PointerId differently to the last + * event seen when mTracker accepted the gesture, we flush all events and go to LISTEN. + * 4) SYNTHESIZE: + * The first motion event found will be turned into a ACTION_DOWN event, all previous events + * will be discarded. + * 5) POSTSYNTHESIZE: + * mSyntheticDownTime != -1 + * All following events will have the down time set to the synthesized ACTION_DOWN event time + * until an ACTION_UP or ACTION_CANCEL is encountered and the state is reset to LISTEN. + * 6) DROP: + * All following events will be discarded. If there is an ACTION_UP, _CANCEL + * we go to LISTEN state. + *

    + * If you are reading this within Java Doc, you are doing something wrong ;) + */ +public class EdgeGestureInputFilter implements IInputFilter { + /* WARNING!! The IInputFilter interface is used directly, there is no Binder between this and + * the InputDispatcher. + * This is fine, because it prevents unnecessary parceling, but beware: + * This means we are running on the dispatch or listener thread of the input dispatcher. Every + * cycle we waste here adds to the overall input latency. + */ + private static final String TAG = "EdgeGestureInputFilter"; + private static final boolean DEBUG = false; + private static final boolean DEBUG_INPUT = false; + // TODO: Should be turned off in final commit + private static final boolean SYSTRACE = false; + + private final Handler mHandler; + + private IInputFilterHost mHost = null; // dispatcher thread + + private static final class MotionEventInfo { + private static final int MAX_POOL_SIZE = 16; + + private static final Object sLock = new Object(); + private static MotionEventInfo sPool; + private static int sPoolSize; + + private boolean mInPool; + + public static MotionEventInfo obtain(MotionEvent event, int policyFlags) { + synchronized (sLock) { + MotionEventInfo info; + if (sPoolSize > 0) { + sPoolSize--; + info = sPool; + sPool = info.next; + info.next = null; + info.mInPool = false; + } else { + info = new MotionEventInfo(); + } + info.initialize(event, policyFlags); + return info; + } + } + + private void initialize(MotionEvent event, int policyFlags) { + this.event = MotionEvent.obtain(event); + this.policyFlags = policyFlags; + cachedTimeMillis = SystemClock.uptimeMillis(); + } + + public void recycle() { + synchronized (sLock) { + if (mInPool) { + throw new IllegalStateException("Already recycled."); + } + clear(); + if (sPoolSize < MAX_POOL_SIZE) { + sPoolSize++; + next = sPool; + sPool = this; + mInPool = true; + } + } + } + + private void clear() { + event.recycle(); + event = null; + policyFlags = 0; + } + + public MotionEventInfo next; + public MotionEvent event; + public int policyFlags; + public long cachedTimeMillis; + } + private final Object mLock = new Object(); + private MotionEventInfo mMotionEventQueue; // guarded by mLock + private MotionEventInfo mMotionEventQueueTail; // guarded by mLock + /* DEBUG */ + private int mMotionEventQueueCountDebug; // guarded by mLock + + private int mDeviceId; // dispatcher only + private enum State { + LISTEN, DETECTING, LOCKED, SYNTHESIZE, POSTSYNTHESIZE, DROP; + } + private State mState = State.LISTEN; // guarded by mLock + private EdgeGestureTracker mTracker; // guarded by mLock + private volatile int mPositions; // written by handler / read by dispatcher + private volatile int mSensitivity; // written by handler / read by dispatcher + + // only used by dispatcher + private long mSyntheticDownTime = -1; + private PointerCoords[] mTempPointerCoords = new PointerCoords[1]; + private PointerProperties[] mTempPointerProperties = new PointerProperties[1]; + + public EdgeGestureInputFilter(Context context, Handler handler) { + mHandler = handler; + + final Resources res = context.getResources(); + mTracker = new EdgeGestureTracker(res.getDimensionPixelSize( + R.dimen.edge_gesture_trigger_thickness), + res.getDimensionPixelSize(R.dimen.edge_gesture_trigger_distance), + res.getDimensionPixelSize(R.dimen.edge_gesture_perpendicular_distance)); + mTracker.setOnActivationListener(new OnActivationListener() { + public void onActivation(MotionEvent event, int touchX, int touchY, EdgeGesturePosition position) { + // mLock is held by #processMotionEvent + mHandler.obtainMessage(EdgeGestureService.MSG_EDGE_GESTURE_ACTIVATION, + touchX, touchY, position).sendToTarget(); + mState = State.LOCKED; + } + }); + mTempPointerCoords[0] = new PointerCoords(); + mTempPointerProperties[0] = new PointerProperties(); + } + + // called from handler thread (lock taken) + public void updateDisplay(Display display, DisplayInfo displayInfo) { + synchronized (mLock) { + mTracker.updateDisplay(display); + } + } + + // called from handler thread (lock taken) + public void updatePositions(int positions) { + mPositions = positions; + } + + // called from handler thread (lock taken) + public void updateSensitivity(int sensitivity) { + mSensitivity = sensitivity; + } + + // called from handler thread + public boolean unlockFilter() { + synchronized (mLock) { + if (mState == State.LOCKED) { + mState = State.SYNTHESIZE; + return true; + } + } + return false; + } + + public boolean dropSequence() { + synchronized (mLock) { + if (mState == State.LOCKED) { + mState = State.DROP; + return true; + } + } + return false; + } + + /** + * Called to enqueue the input event for filtering. + * The event must be recycled after the input filter processed it. + * This method is guaranteed to be non-reentrant. + * + * @see InputFilter#filterInputEvent(InputEvent, int) + * @param event The input event to enqueue. + */ + // called by the input dispatcher thread + public void filterInputEvent(InputEvent event, int policyFlags) throws RemoteException { + if (SYSTRACE) { + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "filterInputEvent"); + } + try { + if (((event.getSource() & InputDevice.SOURCE_TOUCHSCREEN) + != InputDevice.SOURCE_TOUCHSCREEN) + || !(event instanceof MotionEvent)) { + sendInputEvent(event, policyFlags); + return; + } + if (DEBUG_INPUT) { + Slog.d(TAG, "Received event: " + event + ", policyFlags=0x" + + Integer.toHexString(policyFlags)); + } + MotionEvent motionEvent = (MotionEvent) event; + final int deviceId = event.getDeviceId(); + if (deviceId != mDeviceId) { + processDeviceSwitch(deviceId, motionEvent, policyFlags); + } else { + if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) { + synchronized (mLock) { + clearAndResetStateLocked(false, true); + } + } + processMotionEvent(motionEvent, policyFlags); + } + } finally { + event.recycle(); + if (SYSTRACE) { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); + } + } + } + + private void processDeviceSwitch(int deviceId, MotionEvent motionEvent, int policyFlags) { + if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { + mDeviceId = deviceId; + synchronized (mLock) { + clearAndResetStateLocked(true, false); + processMotionEvent(motionEvent, policyFlags); + } + } else { + sendInputEvent(motionEvent, policyFlags); + } + } + + private void processMotionEvent(MotionEvent motionEvent, int policyFlags) { + final int action = motionEvent.getActionMasked(); + + synchronized (mLock) { + switch (mState) { + case LISTEN: + if (action == MotionEvent.ACTION_DOWN) { + boolean hit = mPositions != 0 + && mTracker.start(motionEvent, mPositions, mSensitivity); + if (DEBUG) Slog.d(TAG, "start:" + hit); + if (hit) { + // cache the down event + cacheDelayedMotionEventLocked(motionEvent, policyFlags); + mState = State.DETECTING; + return; + } + } + sendInputEvent(motionEvent, policyFlags); + break; + case DETECTING: + cacheDelayedMotionEventLocked(motionEvent, policyFlags); + if (action == MotionEvent.ACTION_MOVE) { + if (mTracker.move(motionEvent)) { + // return: the tracker is either detecting or triggered onActivation + return; + } + } + if (DEBUG) { + Slog.d(TAG, "move: reset!"); + } + clearAndResetStateLocked(false, true); + break; + case LOCKED: + cacheDelayedMotionEventLocked(motionEvent, policyFlags); + if (action != MotionEvent.ACTION_MOVE) { + clearAndResetStateLocked(false, true); + } + break; + case SYNTHESIZE: + if (action == MotionEvent.ACTION_MOVE) { + clearDelayedMotionEventsLocked(); + sendSynthesizedMotionEventLocked(motionEvent, policyFlags); + mState = State.POSTSYNTHESIZE; + } else { + // This is the case where a race condition caught us: We already + // returned the handler thread that it is all right to call + // #gainTouchFocus(), but apparently this was wrong, as the gesture + // was canceled now. + clearAndResetStateLocked(false, true); + } + break; + case POSTSYNTHESIZE: + motionEvent.setDownTime(mSyntheticDownTime); + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + mState = State.LISTEN; + mSyntheticDownTime = -1; + } + sendInputEvent(motionEvent, policyFlags); + break; + case DROP: + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + clearDelayedMotionEventsLocked(); + mState = State.LISTEN; + } + break; + } + } + } + + private void clearAndResetStateLocked(boolean force, boolean shift) { + // ignore soft reset in POSTSYNTHESIZE, because we need to tamper with + // the event stream and going to LISTEN after an ACTION_UP anyway + if (!force && (mState == State.POSTSYNTHESIZE)) { + return; + } + switch (mState) { + case LISTEN: + // this is a nop + break; + case DETECTING: + mTracker.reset(); + // intentionally no break here + case LOCKED: + case SYNTHESIZE: + sendDelayedMotionEventsLocked(shift); + break; + case POSTSYNTHESIZE: + // hard reset (this will break the event stream) + Slog.w(TAG, "Quit POSTSYNTHESIZE without ACTION_UP from ACTION_DOWN at " + + mSyntheticDownTime); + mSyntheticDownTime = -1; + break; + } + // if there are future events that need to be tampered with, goto POSTSYNTHESIZE + mState = mSyntheticDownTime == -1 ? State.LISTEN : State.POSTSYNTHESIZE; + } + + private void sendInputEvent(InputEvent event, int policyFlags) { + try { + mHost.sendInputEvent(event, policyFlags); + } catch (RemoteException e) { + /* ignore */ + } + } + + private void cacheDelayedMotionEventLocked(MotionEvent event, int policyFlags) { + MotionEventInfo info = MotionEventInfo.obtain(event, policyFlags); + if (mMotionEventQueue == null) { + mMotionEventQueue = info; + } else { + mMotionEventQueueTail.next = info; + } + mMotionEventQueueTail = info; + mMotionEventQueueCountDebug++; + if (SYSTRACE) { + Trace.traceCounter(Trace.TRACE_TAG_INPUT, "meq", mMotionEventQueueCountDebug); + } + } + + private void sendDelayedMotionEventsLocked(boolean shift) { + while (mMotionEventQueue != null) { + MotionEventInfo info = mMotionEventQueue; + mMotionEventQueue = info.next; + + if (DEBUG) { + Slog.d(TAG, "Replay event: " + info.event); + } + mMotionEventQueueCountDebug--; + if (SYSTRACE) { + Trace.traceCounter(Trace.TRACE_TAG_INPUT, "meq", mMotionEventQueueCountDebug); + } + if (shift) { + final long offset = SystemClock.uptimeMillis() - info.cachedTimeMillis; + if (info.event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mSyntheticDownTime = info.event.getDownTime() + offset; + } + sendMotionEventWithOffsetLocked(info.event, info.policyFlags, mSyntheticDownTime, offset); + if (info.event.getActionMasked() == MotionEvent.ACTION_UP) { + mSyntheticDownTime = -1; + } + } else { + sendInputEvent(info.event, info.policyFlags); + } + info.recycle(); + } + mMotionEventQueueTail = null; + } + + private void clearDelayedMotionEventsLocked() { + while (mMotionEventQueue != null) { + MotionEventInfo next = mMotionEventQueue.next; + mMotionEventQueue.recycle(); + mMotionEventQueue = next; + } + mMotionEventQueueTail = null; + mMotionEventQueueCountDebug = 0; + if (SYSTRACE) { + Trace.traceCounter(Trace.TRACE_TAG_INPUT, "meq", mMotionEventQueueCountDebug); + } + } + + private void sendMotionEventWithOffsetLocked(MotionEvent event, int policyFlags, + long downTime, long offset) { + final int pointerCount = event.getPointerCount(); + PointerCoords[] coords = getTempPointerCoordsWithMinSizeLocked(pointerCount); + PointerProperties[] properties = getTempPointerPropertiesWithMinSizeLocked(pointerCount); + for (int i = 0; i < pointerCount; i++) { + event.getPointerCoords(i, coords[i]); + event.getPointerProperties(i, properties[i]); + } + final long eventTime = event.getEventTime() + offset; + sendInputEvent(MotionEvent.obtain(downTime, eventTime, event.getAction(), pointerCount, + properties, coords, event.getMetaState(), event.getButtonState(), 1.0f, 1.0f, + event.getDeviceId(), event.getEdgeFlags(), event.getSource(), event.getFlags()), + policyFlags); + } + + private PointerCoords[] getTempPointerCoordsWithMinSizeLocked(int size) { + final int oldSize = mTempPointerCoords.length; + if (oldSize < size) { + PointerCoords[] oldTempPointerCoords = mTempPointerCoords; + mTempPointerCoords = new PointerCoords[size]; + System.arraycopy(oldTempPointerCoords, 0, mTempPointerCoords, 0, oldSize); + } + for (int i = oldSize; i < size; i++) { + mTempPointerCoords[i] = new PointerCoords(); + } + return mTempPointerCoords; + } + + private PointerProperties[] getTempPointerPropertiesWithMinSizeLocked(int size) { + final int oldSize = mTempPointerProperties.length; + if (oldSize < size) { + PointerProperties[] oldTempPointerProperties = mTempPointerProperties; + mTempPointerProperties = new PointerProperties[size]; + System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, oldSize); + } + for (int i = oldSize; i < size; i++) { + mTempPointerProperties[i] = new PointerProperties(); + } + return mTempPointerProperties; + } + + private void sendSynthesizedMotionEventLocked(MotionEvent event, int policyFlags) { + if (event.getPointerCount() == 1) { + event.getPointerCoords(0, mTempPointerCoords[0]); + event.getPointerProperties(0, mTempPointerProperties[0]); + MotionEvent down = MotionEvent.obtain(event.getEventTime(), event.getEventTime(), + MotionEvent.ACTION_DOWN, 1, mTempPointerProperties, mTempPointerCoords, + event.getMetaState(), event.getButtonState(), + 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(), + event.getSource(), event.getFlags()); + Slog.d(TAG, "Synthesized event:" + down); + sendInputEvent(down, policyFlags); + mSyntheticDownTime = event.getEventTime(); + } else { + Slog.w(TAG, "Could not synthesize MotionEvent, this will drop all following events!"); + } + } + + // should never be called + public IBinder asBinder() { + throw new UnsupportedOperationException(); + } + + // called by the input dispatcher thread + public void install(IInputFilterHost host) throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "EdgeGesture input filter installed."); + } + mHost = host; + synchronized (mLock) { + clearAndResetStateLocked(true, false); + } + } + + // called by the input dispatcher thread + public void uninstall() throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "EdgeGesture input filter uninstalled."); + } + } + + // called by a Binder thread + public void dump(PrintWriter pw, String prefix) { + synchronized (mLock) { + pw.print(prefix); + pw.println("mState=" + mState.name()); + pw.print(prefix); + pw.println("mPositions=0x" + Integer.toHexString(mPositions)); + pw.print(prefix); + pw.println("mQueue=" + mMotionEventQueueCountDebug + " items"); + } + } +} diff --git a/services/java/com/android/server/gesture/EdgeGestureService.java b/services/java/com/android/server/gesture/EdgeGestureService.java new file mode 100644 index 0000000000000..9dc9ac859260c --- /dev/null +++ b/services/java/com/android/server/gesture/EdgeGestureService.java @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project (Jens Doll) + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.server.gesture; + +import static com.android.internal.util.gesture.EdgeServiceConstants.POSITION_MASK; +import static com.android.internal.util.gesture.EdgeServiceConstants.SENSITIVITY_DEFAULT; +import static com.android.internal.util.gesture.EdgeServiceConstants.SENSITIVITY_MASK; +import static com.android.internal.util.gesture.EdgeServiceConstants.SENSITIVITY_NONE; +import static com.android.internal.util.gesture.EdgeServiceConstants.SENSITIVITY_SHIFT; +import static com.android.internal.util.gesture.EdgeServiceConstants.LONG_LIVING; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Parcel; +import android.os.RemoteException; +import android.service.gesture.IEdgeGestureActivationListener; +import android.service.gesture.IEdgeGestureHostCallback; +import android.service.gesture.IEdgeGestureService; +import android.util.Slog; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.WindowManager; + +import com.android.internal.util.gesture.EdgeGesturePosition; +import com.android.server.gesture.EdgeGestureInputFilter; +import com.android.server.input.InputManagerService; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * A system service to track and handle edge swipe gestures. This service interacts with + * the {@link InputManagerService} to do all the dirty work for listeners: + *

  • Installing an input filter and listen for edge swipe gestures
  • + *
  • Removing those gestures from the input stream
  • + *
  • Transferring touch focus to new recipient
  • + */ +public class EdgeGestureService extends IEdgeGestureService.Stub { + public static final String TAG = "EdgeGestureService"; + public static final boolean DEBUG = false; + public static final boolean DEBUG_INPUT = false; + + public static final int MSG_EDGE_GESTURE_ACTIVATION = 32023; + public static final int MSG_UPDATE_SERVICE = 32025; + + private final Context mContext; + private final InputManagerService mInputManager; + + private final HandlerThread mHandlerThread = new HandlerThread("EdgeGestureHandler"); + private Handler mHandler; + + // Lock for mInputFilter, activations and listener related variables + private final Object mLock = new Object(); + private EdgeGestureInputFilter mInputFilter; + + private int mGlobalPositions = 0; + private int mGlobalSensitivity = 3; + + private final class EdgeGestureActivationListenerRecord extends IEdgeGestureHostCallback.Stub implements DeathRecipient { + private boolean mActive; + + public EdgeGestureActivationListenerRecord(IEdgeGestureActivationListener listener) { + this.listener = listener; + this.positions = 0; + } + + public void binderDied() { + removeListenerRecord(this); + } + + private void updateFlags(int flags) { + this.positions = flags & POSITION_MASK; + this.sensitivity = (flags & SENSITIVITY_MASK) >> SENSITIVITY_SHIFT; + this.longLiving = (flags & LONG_LIVING) != 0; + } + + private boolean eligibleForActivation(int positionFlag) { + return (positions & positionFlag) != 0; + } + + private boolean notifyEdgeGestureActivation(int touchX, int touchY, EdgeGesturePosition position) { + if ((positions & position.FLAG) != 0) { + try { + mActive = true; + listener.onEdgeGestureActivation(touchX, touchY, position.INDEX, 0); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify process, assuming it died.", e); + mActive = false; + binderDied(); + } + } + return mActive; + } + + // called through Binder + public boolean gainTouchFocus(IBinder windowToken) { + if (DEBUG) { + Slog.d(TAG, "Gain touch focus for " + windowToken); + } + if (mActive) { + return mInputFilter.unlockFilter(); + } + return false; + } + + public boolean dropEventsUntilLift() { + if (DEBUG) { + Slog.d(TAG, "Will drop all next events till touch up"); + } + if (mActive) { + return mInputFilter.dropSequence(); + } + return false; + } + + // called through Binder + public void restoreListenerState() throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "Restore listener state"); + } + if (mActive) { + mInputFilter.unlockFilter(); + mActive = false; + synchronized (mLock) { + // restore input filter state by updating + mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE); + } + } + } + + public boolean isActive() { + return mActive; + } + + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); + pw.print("mPositions=0x" + Integer.toHexString(positions)); + pw.println(" mActive=" + mActive); + pw.print(prefix); + pw.println("mBinder=" + listener); + } + + public int positions; + public int sensitivity; + public final IEdgeGestureActivationListener listener; + public boolean longLiving = false; + } + private final List mEdgeGestureActivationListener = + new ArrayList(); + // end of lock guarded variables + + private DisplayObserver mDisplayObserver; + + // called by system server + public EdgeGestureService(Context context, InputManagerService inputManager) { + mContext = context; + mInputManager = inputManager; + } + + // called by system server + public void systemReady() { + if (DEBUG) Slog.d(TAG, "Starting the edge gesture capture thread ..."); + + mHandlerThread.start(); + mHandler = new H(mHandlerThread.getLooper()); + mHandler.post(new Runnable() { + @Override + public void run() { + android.os.Process.setThreadPriority( + android.os.Process.THREAD_PRIORITY_FOREGROUND); + android.os.Process.setCanSelfBackground(false); + } + }); + mDisplayObserver = new DisplayObserver(mContext, mHandler); + // check if anyone registered during startup + mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE); + } + + + private void updateMonitoring() { + synchronized(mLock) { + mGlobalPositions = 0; + mGlobalSensitivity = SENSITIVITY_NONE; + boolean someLongLiving = false; + int activePositions = 0; + for (EdgeGestureActivationListenerRecord temp : mEdgeGestureActivationListener) { + mGlobalPositions |= temp.positions; + if (temp.isActive()) { + activePositions |= temp.positions; + } + if (temp.sensitivity != SENSITIVITY_NONE) { + mGlobalSensitivity = Math.max(mGlobalSensitivity, temp.sensitivity); + } + someLongLiving |= temp.longLiving; + } + boolean havePositions = mGlobalPositions != 0; + mGlobalPositions &= ~activePositions; + // if no special sensitivity is requested, we settle on DEFAULT + if (mGlobalSensitivity == SENSITIVITY_NONE) { + mGlobalSensitivity = SENSITIVITY_DEFAULT; + } + + if (mInputFilter == null && havePositions) { + enforceMonitoringLocked(); + } else if (mInputFilter != null && !havePositions && !someLongLiving) { + shutdownMonitoringLocked(); + } + } + } + + private void enforceMonitoringLocked() { + if (DEBUG) { + Slog.d(TAG, "Attempting to start monitoring input events ..."); + } + mInputFilter = new EdgeGestureInputFilter(mContext, mHandler); + mInputManager.setInputFilter(mInputFilter); + mDisplayObserver.observe(); + } + + private void shutdownMonitoringLocked() { + if (DEBUG) { + Slog.d(TAG, "Shutting down monitoring input events ..."); + } + mDisplayObserver.unobserve(); + mInputManager.setInputFilter(mInputFilter); + mInputFilter = null; + } + + // called through Binder + public IEdgeGestureHostCallback registerEdgeGestureActivationListener(IEdgeGestureActivationListener listener) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.INJECT_EVENTS) + != PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "Permission Denial: can't register from from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); + return null; + } + + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + + EdgeGestureActivationListenerRecord record = null; + synchronized(mLock) { + record = findListenerRecordLocked(listener.asBinder()); + if (record == null) { + record = new EdgeGestureActivationListenerRecord(listener); + try { + listener.asBinder().linkToDeath(record, 0); + } catch (RemoteException e) { + Slog.w(TAG, "Recipient died during registration pid=" + Binder.getCallingPid()); + return null; + } + mEdgeGestureActivationListener.add(record); + } + } + return record; + } + + // called through Binder + public void updateEdgeGestureActivationListener(IBinder listener, int positionFlags) { + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + synchronized(mLock) { + EdgeGestureActivationListenerRecord record = findListenerRecordLocked(listener); + if (record == null) { + Slog.w(TAG, "Unknown listener on update listener. Register first?"); + throw new IllegalStateException("listener not registered"); + } + record.updateFlags(positionFlags); + // update input filter only when #systemReady() was called + if (mHandler != null) { + mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE); + } + } + } + + private EdgeGestureActivationListenerRecord findListenerRecordLocked(IBinder listener) { + for (EdgeGestureActivationListenerRecord record : mEdgeGestureActivationListener) { + if (record.listener.asBinder().equals(listener)) { + return record; + } + } + return null; + } + + private void removeListenerRecord(EdgeGestureActivationListenerRecord record) { + synchronized(mLock) { + mEdgeGestureActivationListener.remove(record); + // restore input filter state by updating + mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE); + } + } + + // called by handler thread + private boolean propagateActivation(int touchX, int touchY, EdgeGesturePosition position) { + synchronized(mLock) { + EdgeGestureActivationListenerRecord target = null; + for (EdgeGestureActivationListenerRecord record : mEdgeGestureActivationListener) { + if (record.eligibleForActivation(position.FLAG)) { + target = record; + break; + } + } + // NOTE: We can do this here because the #onGestureActivation() is a oneway + // Binder call. This means we do not block with holding the mListenerLock!!! + // If this ever change, this needs to be adjusted and if you don't know what + // this means, you should probably not mess around with this code, anyway. + if (target != null && !target.notifyEdgeGestureActivation(touchX, touchY, position)) { + target = null; + } + return target != null; + } + } + + private final class H extends Handler { + public H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message m) { + switch (m.what) { + case MSG_EDGE_GESTURE_ACTIVATION: + if (DEBUG) { + Slog.d(TAG, "Activating edge gesture on " + m.obj.toString()); + } + // Since input filter runs asynchronously to us, double activation may happen + // theoretically. Take the safe route here. + removeMessages(MSG_EDGE_GESTURE_ACTIVATION); + if (propagateActivation(m.arg1, m.arg2, (EdgeGesturePosition) m.obj)) { + // switch off activated positions + updateMonitoring(); + updateServiceHandler(mGlobalPositions, mGlobalSensitivity); + } + break; + case MSG_UPDATE_SERVICE: + updateMonitoring(); + if (DEBUG) { + Slog.d(TAG, "Updating positions 0x" + Integer.toHexString(mGlobalPositions) + + " sensitivity: " + mGlobalSensitivity); + } + updateServiceHandler(mGlobalPositions, mGlobalSensitivity); + break; + } + } + + private void updateServiceHandler(int positions, int sensitivity) { + synchronized (mLock) { + if (mInputFilter != null) { + mInputFilter.updatePositions(positions); + mInputFilter.updateSensitivity(sensitivity); + } + } + } + } + + private final class DisplayObserver implements DisplayListener { + private final Handler mHandler; + private final DisplayManager mDisplayManager; + + private final Display mDefaultDisplay; + private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo(); + + public DisplayObserver(Context context, Handler handler) { + mHandler = handler; + mDisplayManager = (DisplayManager) context.getSystemService( + Context.DISPLAY_SERVICE); + final WindowManager windowManager = (WindowManager) context.getSystemService( + Context.WINDOW_SERVICE); + + mDefaultDisplay = windowManager.getDefaultDisplay(); + updateDisplayInfo(); + } + + private void updateDisplayInfo() { + if (DEBUG) { + Slog.d(TAG, "Updating display information ..."); + } + if (mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) { + synchronized (mLock) { + if (mInputFilter != null) { + mInputFilter.updateDisplay(mDefaultDisplay, mDefaultDisplayInfo); + } + } + } else { + Slog.e(TAG, "Default display is not valid."); + } + } + + public void observe() { + mDisplayManager.registerDisplayListener(this, mHandler); + updateDisplayInfo(); + } + + public void unobserve() { + mDisplayManager.unregisterDisplayListener(this); + } + + @Override + public void onDisplayAdded(int displayId) { + /* do noting */ + } + + @Override + public void onDisplayRemoved(int displayId) { + /* do nothing */ + } + + @Override + public void onDisplayChanged(int displayId) { + updateDisplayInfo(); + } + } + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + try { + return super.onTransact(code, data, reply, flags); + } catch (RuntimeException e) { + // let's log all exceptions we do not know about. + if (!(e instanceof IllegalArgumentException || e instanceof IllegalStateException)) { + Slog.e(TAG, "EdgeGestureService crashed: ", e); + } + throw e; + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump EdgeGestureService from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + pw.println("EDGE GESTURE SERVICE (dumpsys edgegestureservice)\n"); + synchronized(mLock) { + pw.println(" mInputFilter=" + mInputFilter); + if (mInputFilter != null) { + mInputFilter.dump(pw, " "); + } + pw.println(" mGlobalPositions=0x" + Integer.toHexString(mGlobalPositions)); + pw.println(" mGlobalSensitivity=" + mGlobalSensitivity); + int i = 0; + for (EdgeGestureActivationListenerRecord record : mEdgeGestureActivationListener) { + if (record.isActive()) { + pw.println(" Active record: #" + (i + 1)); + } + } + i = 0; + for (EdgeGestureActivationListenerRecord record : mEdgeGestureActivationListener) { + pw.println(" Listener #" + i + ":"); + record.dump(pw, " "); + i++; + } + } + } +} diff --git a/services/java/com/android/server/gesture/EdgeGestureTracker.java b/services/java/com/android/server/gesture/EdgeGestureTracker.java new file mode 100644 index 0000000000000..17cd95eda0d0b --- /dev/null +++ b/services/java/com/android/server/gesture/EdgeGestureTracker.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project (Jens Doll) + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.server.gesture; + +import android.graphics.Point; +import android.os.SystemClock; +import android.util.Slog; +import android.view.Display; +import android.view.MotionEvent; + +import com.android.internal.util.gesture.EdgeGesturePosition; +import com.android.internal.util.gesture.EdgeServiceConstants; + +/** + * A simple {@link MotionEvent} tracker class. The main aim of this tracker is to + * reject gestures as fast as possible, so there is only a small amount of events + * that will be delayed. + */ +public class EdgeGestureTracker { + public final static String TAG = "EdgeGestureTracker"; + public final static boolean DEBUG = false; + + public final static long TRIGGER_TIME_MS = 140; + public final static int PIXEL_SWIPE_OFFTAKE_SLOP = 2; + + private final int mBaseThickness; + private final int mBaseTriggerDistance; + private final int mBasePerpendicularDistance; + + private int mThickness; + private int mTriggerDistance; + private int mPerpendicularDistance; + private int mGracePeriodDistance; + private long mTimeOut; + + private int mDisplayWidth; + private int mDisplayHeight; + + private boolean mActive; + private EdgeGesturePosition mPosition; + private long mDownTime; + private int mInitialX; + private int mInitialY; + private int mOffTake; + private int mGracePeriod; + + public interface OnActivationListener { + public void onActivation(MotionEvent event, int touchX, int touchY, EdgeGesturePosition position); + } + private OnActivationListener mActivationListener; + + public EdgeGestureTracker(int thickness, int distance, int perpendicular) { + if (DEBUG) { + Slog.d(TAG, "init: " + thickness + "," + distance); + } + mBaseThickness = thickness; + mBaseTriggerDistance = distance; + mBasePerpendicularDistance = perpendicular; + setSensitivity(0); + } + + private void setSensitivity(int sensitivity) { + float factor = 0.0f; + if (sensitivity >= 1) { + factor = (sensitivity - 1) / 4.0f; + } + if (DEBUG) { + Slog.d(TAG, "sensitivity: " + sensitivity + " => factor:" + factor); + } + // default values (without overlay): + // 140ms ... 210ms + mTimeOut = (long) (TRIGGER_TIME_MS * (factor + 1.0f)); + // 12dp ... 18dp + mThickness = (int) (mBaseThickness * (factor + 1.0f)); + // 12dp ... 6dp + mTriggerDistance = (int) (mBaseTriggerDistance * (1.0f - factor)); + mPerpendicularDistance = (int) (mBasePerpendicularDistance * (1.0f - factor)); + mGracePeriodDistance = (int) (mThickness / 3.0f); + } + + public void setOnActivationListener(OnActivationListener listener) { + mActivationListener = listener; + } + + public void reset() { + mActive = false; + } + + public void updateDisplay(Display display) { + Point outSize = new Point(0,0); + display.getRealSize(outSize); + mDisplayWidth = outSize.x; + mDisplayHeight = outSize.y; + if (DEBUG) { + Slog.d(TAG, "new display: " + mDisplayWidth + "," + mDisplayHeight); + } + } + + public boolean start(MotionEvent motionEvent, int positions, int sensitivity) { + final boolean unrestricted = (positions & EdgeServiceConstants.UNRESTRICTED) != 0; + final int x = (int) motionEvent.getX(); + final float fx = motionEvent.getX() / mDisplayWidth; + final int y = (int) motionEvent.getY(); + final float fy = motionEvent.getY() / mDisplayHeight; + + // calculate trigger geometry based on sensitivity + setSensitivity(sensitivity); + + if ((positions & EdgeGesturePosition.LEFT.FLAG) != 0) { + if (x < mThickness && (unrestricted || (fy > 0.1f && fy < 0.9f))) { + startWithPosition(motionEvent, EdgeGesturePosition.LEFT); + return true; + } + } + if ((positions & EdgeGesturePosition.BOTTOM.FLAG) != 0) { + if (y > mDisplayHeight - mThickness && (unrestricted || (fx > 0.1f && fx < 0.9f))) { + startWithPosition(motionEvent, EdgeGesturePosition.BOTTOM); + return true; + } + } + if ((positions & EdgeGesturePosition.RIGHT.FLAG) != 0) { + if (x > mDisplayWidth - mThickness && (unrestricted || (fy > 0.1f && fy < 0.9f))) { + startWithPosition(motionEvent, EdgeGesturePosition.RIGHT); + return true; + } + } + if ((positions & EdgeGesturePosition.TOP.FLAG) != 0) { + if (y < mThickness && (unrestricted || (fx > 0.1f && fx < 0.9f))) { + startWithPosition(motionEvent, EdgeGesturePosition.TOP); + return true; + } + } + return false; + } + + private void startWithPosition(MotionEvent motionEvent, EdgeGesturePosition position) { + if (DEBUG) { + Slog.d(TAG, "start tracking from " + position.name()); + } + + mDownTime = motionEvent.getDownTime(); + this.mPosition = position; + mInitialX = (int) motionEvent.getX(); + mInitialY = (int) motionEvent.getY(); + switch (position) { + case LEFT: + mGracePeriod = mGracePeriodDistance; + mOffTake = mInitialX - PIXEL_SWIPE_OFFTAKE_SLOP; + break; + case BOTTOM: + mOffTake = mInitialY + PIXEL_SWIPE_OFFTAKE_SLOP; + break; + case RIGHT: + mGracePeriod = mDisplayWidth - mGracePeriodDistance; + mOffTake = mInitialX + PIXEL_SWIPE_OFFTAKE_SLOP; + break; + case TOP: + mOffTake = mInitialY - PIXEL_SWIPE_OFFTAKE_SLOP; + break; + } + mActive = true; + } + + public boolean move(MotionEvent motionEvent) { + if (!mActive || motionEvent.getEventTime() - mDownTime > mTimeOut) { + Slog.d(TAG, "edge gesture timeout: " + (motionEvent.getEventTime() - mDownTime)); + mActive = false; + return false; + } + + final int x = (int) motionEvent.getX(); + final int y = (int) motionEvent.getY(); + final int deltaX = x - mInitialX; + final int deltaY = y - mInitialY; + + if (DEBUG) { + Slog.d(TAG, "move at " + x + "," + y + " " + deltaX + "," + deltaY); + } + + boolean loaded = false; + switch (mPosition) { + case LEFT: + if (x < mGracePeriod) { + mInitialY = y; + } + if (deltaY < mPerpendicularDistance && deltaY > -mPerpendicularDistance + && x >= mOffTake) { + if (deltaX < mTriggerDistance) { + mOffTake = x - PIXEL_SWIPE_OFFTAKE_SLOP; + return true; + } + loaded = true; + } + break; + case BOTTOM: + if (deltaX < mPerpendicularDistance && deltaX > -mPerpendicularDistance + && y <= mOffTake) { + if (deltaY > -mTriggerDistance) { + mOffTake = y + PIXEL_SWIPE_OFFTAKE_SLOP; + return true; + } + loaded = true; + } + break; + case RIGHT: + if (x > mGracePeriod) { + mInitialY = y; + } + if (deltaY < mPerpendicularDistance && deltaY > -mPerpendicularDistance + && x <= mOffTake) { + if (deltaX > -mTriggerDistance) { + mOffTake = x + PIXEL_SWIPE_OFFTAKE_SLOP; + return true; + } + loaded = true; + } + break; + case TOP: + if (deltaX < mPerpendicularDistance && deltaX > -mPerpendicularDistance + && y >= mOffTake) { + if (deltaY < mTriggerDistance) { + mOffTake = y - PIXEL_SWIPE_OFFTAKE_SLOP; + return true; + } + loaded = true; + } + break; + } + mActive = false; + if (loaded && mActivationListener != null) { + if (DEBUG) { + Slog.d(TAG, "activate at " + x + "," + y + " " + mPosition + " within " + + (SystemClock.uptimeMillis() - mDownTime) + "ms"); + } + mActivationListener.onActivation(motionEvent, x, y, mPosition); + } + return loaded; + } +} \ No newline at end of file From 9be443dbdb988a8b9c5c1ba69e82b52003d28c8a Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 2 Jul 2018 00:01:14 +0530 Subject: [PATCH 69/73] Make ThemeAccentsUtils accessible to other packages - Move our ThemeAccentsUtils class to the core/java/com/android/internal/statusbar so we can call it from not only the SystemUI but other packages. This will reduce the amount of code in packages like DU-Tweaks, Updater and Settings. - Consolidate the code with our themes tile Change-Id: Ia77d53802b73ac1157f3cc8ce5887a3e50555382 --- core/java/com/android/internal/statusbar/ThemeAccentUtils.java | 2 +- .../SystemUI/src/com/android/systemui/qs/tiles/ThemeTile.java | 2 +- .../src/com/android/systemui/statusbar/phone/StatusBar.java | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/java/com/android/internal/statusbar/ThemeAccentUtils.java b/core/java/com/android/internal/statusbar/ThemeAccentUtils.java index 233330aaea030..7e8e0f27ce01f 100644 --- a/core/java/com/android/internal/statusbar/ThemeAccentUtils.java +++ b/core/java/com/android/internal/statusbar/ThemeAccentUtils.java @@ -173,7 +173,7 @@ public static void unloadStockDarkTheme(IOverlayManager om, int userId) { public static boolean isUsingWhiteAccent(IOverlayManager om, int userId) { OverlayInfo themeInfo = null; try { - themeInfo = om.getOverlayInfo(ACCENTS[21], + themeInfo = om.getOverlayInfo(ACCENTS[26], userId); } catch (RemoteException e) { e.printStackTrace(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ThemeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ThemeTile.java index a6b3ebbe3be66..1ecb6e2c2f0fc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ThemeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ThemeTile.java @@ -288,7 +288,7 @@ public void onItemClick(AdapterView parent, View view, int position, long id) private ThemeTileItem getThemeItemForStyleMode() { if (ThemeAccentUtils.isUsingDarkTheme(mOverlayManager, mCurrentUserId)) { - return new ThemeTileItem(20, R.color.quick_settings_theme_tile_white, + return new ThemeTileItem(25, R.color.quick_settings_theme_tile_white, R.string.quick_settings_theme_tile_color_white); } else { return new ThemeTileItem(20, R.color.quick_settings_theme_tile_black, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index a3dc900ecfd49..0d09f6b71d505 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -243,6 +243,7 @@ import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.SignalClusterView; import com.android.systemui.statusbar.StatusBarState; +import com.android.internal.statusbar.ThemeAccentUtils; import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.RowInflaterTask; From d555ba5744f5d798d13609d3e973041bef4f7f41 Mon Sep 17 00:00:00 2001 From: SuperDroidBond Date: Tue, 31 Jul 2018 02:25:42 +0530 Subject: [PATCH 70/73] [Base 1/2] QS Tile Icon Accent Toggle Idea taken from -: https://github.com/DotOS/android_frameworks_base/commit/8976f6176971c79ecc46a23475364fcd407bbd4d aex edits:- disabled by default, remove useless overlay Change-Id: I834053e6b118024e1b0609150df641b999fd48f9 --- core/java/android/provider/Settings.java | 8 +++++ packages/SystemUI/res/values/aos_colors.xml | 5 ++++ .../systemui/qs/tileimpl/QSIconViewImpl.java | 4 ++- .../systemui/qs/tileimpl/QSTileImpl.java | 29 +++++++++++++++---- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index fac4a1bd3fbb1..92c2299609551 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4840,6 +4840,14 @@ public boolean validate(String value) { */ public static final String CHOOSER_ACTIVITY_BLACKLIST = "chooser_activity_blacklist"; + /** + ** Whether to show Device Accent on QS on the screen. + ** 0 = OFF + ** 1 = ON + ** @hide + **/ + public static final String QS_TILE_TINTING_ENABLE = "qs_tile_tinting_enable"; + /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. diff --git a/packages/SystemUI/res/values/aos_colors.xml b/packages/SystemUI/res/values/aos_colors.xml index 64d1ae1c5cdba..a6ba48ad184e4 100644 --- a/packages/SystemUI/res/values/aos_colors.xml +++ b/packages/SystemUI/res/values/aos_colors.xml @@ -44,6 +44,11 @@ @android:color/white + + @*android:color/accent_device_default_light + @*android:color/hint_foreground_material_light + @*android:color/foreground_material_light + #4285F4 #FF5252 diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java index c249e3778c0a3..002289821e5f7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -27,6 +27,8 @@ import android.view.View; import android.widget.ImageView; import android.widget.ImageView.ScaleType; +import android.provider.Settings; +import android.os.Handler; import com.android.systemui.R; import com.android.systemui.plugins.qs.QSIconView; @@ -173,7 +175,7 @@ public static void animateGrayScale(int fromColor, int toColor, ImageView iv) { int alpha = (int) (fromAlpha + (toAlpha - fromAlpha) * fraction); int channel = (int) (fromChannel + (toChannel - fromChannel) * fraction); - setTint(iv, Color.argb(alpha, channel, channel, channel)); + setTint(iv, toColor); }); anim.start(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 576a447447b5f..8637e444b5a5a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -36,7 +36,7 @@ import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; - +import android.provider.Settings; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.settingslib.RestrictedLockUtils; @@ -50,6 +50,8 @@ import com.android.systemui.qs.PagedTileLayout.TilePage; import com.android.systemui.qs.QSHost; +import com.android.systemui.R; + import java.util.ArrayList; /** @@ -370,14 +372,31 @@ protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRes public abstract CharSequence getTileLabel(); public static int getColorForState(Context context, int state) { + + boolean enableQsTileTinting = Settings.System.getInt(context.getContentResolver(), + Settings.System.QS_TILE_TINTING_ENABLE, 0) == 1; + switch (state) { case Tile.STATE_UNAVAILABLE: - return Utils.getDisabled(context, - Utils.getColorAttr(context, android.R.attr.colorForeground)); + if (enableQsTileTinting) { + return Utils.getDisabled(context, + context.getResources().getColor(R.color.qs_tiles_unavailable_tint)); + } else { + return Utils.getDisabled(context, + Utils.getColorAttr(context, android.R.attr.colorForeground)); + } case Tile.STATE_INACTIVE: - return Utils.getColorAttr(context, android.R.attr.textColorHint); + if (enableQsTileTinting) { + return Utils.getColorAttr(context, android.R.attr.textColorHint); + } else { + return Utils.getColorAttr(context, android.R.attr.textColorHint); + } case Tile.STATE_ACTIVE: - return Utils.getColorAttr(context, android.R.attr.textColorPrimary); + if (enableQsTileTinting) { + return context.getResources().getColor(R.color.qs_tiles_active_tint); + } else { + return Utils.getColorAttr(context, android.R.attr.textColorPrimary); + } default: Log.e("QSTile", "Invalid state " + state); return 0; From f72806d19124c95a28b0b4cfd6803f7e7d0c77b8 Mon Sep 17 00:00:00 2001 From: Roman Birg Date: Wed, 11 Nov 2015 15:01:41 -0500 Subject: [PATCH 71/73] Base: Add Quick Settings Pulldown options [1/2] PureNexus Edits: *add always quick pulldown option *fix for double finger pulldown Change-Id: I384e751861a9b2f309ee9f25cbdef094aedf792d Signed-off-by: mydongistiny (cherry picked from commit a6017c53b9dc5cb76c39270d94a8704553d1188e) --- .../phone/NotificationPanelView.java | 65 ++++++++++++++++--- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 6c96b72be0bb4..74bc30865e46e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -24,16 +24,22 @@ import android.app.ActivityManager; import android.app.Fragment; import android.app.StatusBarManager; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.database.ContentObserver; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; +import android.net.Uri; +import android.os.Handler; import android.os.PowerManager; +import android.os.UserHandle; +import android.provider.Settings; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.MathUtils; @@ -186,8 +192,6 @@ public Float get(NotificationPanelView object) { private boolean mQsExpandImmediate; private boolean mTwoFingerQsExpandPossible; - private boolean mOneFingerQuickSettingsIntercept; - /** * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still * need to take this into account in our panel height calculation. @@ -249,6 +253,7 @@ public void run() { private boolean mIsLockscreenDoubleTapEnabled; private int mStatusBarHeaderHeight; + private int mOneFingerQuickSettingsIntercept; public NotificationPanelView(Context context, AttributeSet attrs) { super(context, attrs); @@ -929,7 +934,8 @@ private boolean handleQsTouch(MotionEvent event) { mTwoFingerQsExpandPossible = true; } if (mTwoFingerQsExpandPossible && isOpenQsEvent(event) - && event.getY(event.getActionIndex()) < mStatusBarMinHeight) { + && event.getY(event.getActionIndex()) < mStatusBarMinHeight + && mExpandedHeight <= mQsPeekHeight) { MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_QS, 1); mQsExpandImmediate = true; requestPanelHeightUpdate(); @@ -963,18 +969,25 @@ private boolean isOpenQsEvent(MotionEvent event) { && (event.isButtonPressed(MotionEvent.BUTTON_SECONDARY) || event.isButtonPressed(MotionEvent.BUTTON_TERTIARY)); - final float w = getMeasuredWidth(); final float x = event.getX(); - float region = w * 1.f / 3.f; // TODO overlay region fraction? + float region = (w * (1.f/4.f)); // TODO overlay region fraction? boolean showQsOverride = false; - if (mOneFingerQuickSettingsIntercept) { - showQsOverride = isLayoutRtl() ? x < region : w - region < x; + switch (mOneFingerQuickSettingsIntercept) { + case 1: // Right side pulldown + showQsOverride = isLayoutRtl() ? (x < region) : (w - region < x); + break; + case 2: // Left side pulldown + showQsOverride = isLayoutRtl() ? (w - region < x) : (x < region); + break; + case 3: // pull down anywhere + showQsOverride = true; + break; } showQsOverride &= mStatusBarState == StatusBarState.SHADE; - return showQsOverride || twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag; + return twoFingerDrag || showQsOverride || stylusButtonClickDrag || mouseButtonClickDrag; } private void handleQsDown(MotionEvent event) { @@ -2596,6 +2609,42 @@ public boolean hideStatusBarIconsWhenExpanded() { return !isFullWidth() || !mShowIconsWhenExpanded; } + class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + void observe() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.STATUS_BAR_QUICK_QS_PULLDOWN), + false, this, UserHandle.USER_ALL); + update(); + } + + void unobserve() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.unregisterContentObserver(this); + } + + @Override + public void onChange(boolean selfChange) { + update(); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + update(); + } + + public void update() { + ContentResolver resolver = mContext.getContentResolver(); + mOneFingerQuickSettingsIntercept = Settings.System.getIntForUser( + resolver, Settings.System.STATUS_BAR_QUICK_QS_PULLDOWN, 0, + UserHandle.USER_CURRENT); + } + } + private final FragmentListener mFragmentListener = new FragmentListener() { @Override public void onFragmentViewCreated(String tag, Fragment fragment) { From 77d55e72d441bb0312288cc93b09f606a44fd44f Mon Sep 17 00:00:00 2001 From: aoleary Date: Tue, 29 Jan 2019 09:11:13 +0000 Subject: [PATCH 72/73] Revert "Base: Add Quick Settings Pulldown options [1/2]" This reverts commit f72806d19124c95a28b0b4cfd6803f7e7d0c77b8. --- .../phone/NotificationPanelView.java | 65 +++---------------- 1 file changed, 8 insertions(+), 57 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 74bc30865e46e..6c96b72be0bb4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -24,22 +24,16 @@ import android.app.ActivityManager; import android.app.Fragment; import android.app.StatusBarManager; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; -import android.database.ContentObserver; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; -import android.net.Uri; -import android.os.Handler; import android.os.PowerManager; -import android.os.UserHandle; -import android.provider.Settings; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.MathUtils; @@ -192,6 +186,8 @@ public Float get(NotificationPanelView object) { private boolean mQsExpandImmediate; private boolean mTwoFingerQsExpandPossible; + private boolean mOneFingerQuickSettingsIntercept; + /** * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still * need to take this into account in our panel height calculation. @@ -253,7 +249,6 @@ public void run() { private boolean mIsLockscreenDoubleTapEnabled; private int mStatusBarHeaderHeight; - private int mOneFingerQuickSettingsIntercept; public NotificationPanelView(Context context, AttributeSet attrs) { super(context, attrs); @@ -934,8 +929,7 @@ private boolean handleQsTouch(MotionEvent event) { mTwoFingerQsExpandPossible = true; } if (mTwoFingerQsExpandPossible && isOpenQsEvent(event) - && event.getY(event.getActionIndex()) < mStatusBarMinHeight - && mExpandedHeight <= mQsPeekHeight) { + && event.getY(event.getActionIndex()) < mStatusBarMinHeight) { MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_QS, 1); mQsExpandImmediate = true; requestPanelHeightUpdate(); @@ -969,25 +963,18 @@ private boolean isOpenQsEvent(MotionEvent event) { && (event.isButtonPressed(MotionEvent.BUTTON_SECONDARY) || event.isButtonPressed(MotionEvent.BUTTON_TERTIARY)); + final float w = getMeasuredWidth(); final float x = event.getX(); - float region = (w * (1.f/4.f)); // TODO overlay region fraction? + float region = w * 1.f / 3.f; // TODO overlay region fraction? boolean showQsOverride = false; - switch (mOneFingerQuickSettingsIntercept) { - case 1: // Right side pulldown - showQsOverride = isLayoutRtl() ? (x < region) : (w - region < x); - break; - case 2: // Left side pulldown - showQsOverride = isLayoutRtl() ? (w - region < x) : (x < region); - break; - case 3: // pull down anywhere - showQsOverride = true; - break; + if (mOneFingerQuickSettingsIntercept) { + showQsOverride = isLayoutRtl() ? x < region : w - region < x; } showQsOverride &= mStatusBarState == StatusBarState.SHADE; - return twoFingerDrag || showQsOverride || stylusButtonClickDrag || mouseButtonClickDrag; + return showQsOverride || twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag; } private void handleQsDown(MotionEvent event) { @@ -2609,42 +2596,6 @@ public boolean hideStatusBarIconsWhenExpanded() { return !isFullWidth() || !mShowIconsWhenExpanded; } - class SettingsObserver extends ContentObserver { - SettingsObserver(Handler handler) { - super(handler); - } - - void observe() { - ContentResolver resolver = mContext.getContentResolver(); - resolver.registerContentObserver(Settings.System.getUriFor( - Settings.System.STATUS_BAR_QUICK_QS_PULLDOWN), - false, this, UserHandle.USER_ALL); - update(); - } - - void unobserve() { - ContentResolver resolver = mContext.getContentResolver(); - resolver.unregisterContentObserver(this); - } - - @Override - public void onChange(boolean selfChange) { - update(); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - update(); - } - - public void update() { - ContentResolver resolver = mContext.getContentResolver(); - mOneFingerQuickSettingsIntercept = Settings.System.getIntForUser( - resolver, Settings.System.STATUS_BAR_QUICK_QS_PULLDOWN, 0, - UserHandle.USER_CURRENT); - } - } - private final FragmentListener mFragmentListener = new FragmentListener() { @Override public void onFragmentViewCreated(String tag, Fragment fragment) { From aff299d0640abd2292a8f8aad9d9903a899223fc Mon Sep 17 00:00:00 2001 From: aoleary Date: Tue, 29 Jan 2019 13:26:15 +0000 Subject: [PATCH 73/73] Merge tag 'android-8.1.0_r42' into 8.1 --- .../android/content/pm/PackageItemInfo.java | 19 ++-- .../java/android/net/ConnectivityManager.java | 22 +++++ .../android/net/IConnectivityManager.aidl | 1 + .../android/view/WindowManagerPolicy.java | 5 ++ libs/androidfw/ResourceTypes.cpp | 14 ++- .../android/server/ConnectivityService.java | 70 ++++++++++----- .../controllers/ConnectivityController.java | 6 +- .../server/policy/PhoneWindowManager.java | 5 ++ .../policy/keyguard/KeyguardStateMonitor.java | 3 + .../server/wm/RootWindowContainer.java | 8 ++ .../server/wm/WindowManagerService.java | 13 +++ .../server/wm/DisplayContentTests.java | 19 ++++ .../server/wm/TestWindowManagerPolicy.java | 5 +- .../server/ConnectivityServiceTest.java | 87 +++++++++++++++++++ 14 files changed, 241 insertions(+), 36 deletions(-) diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java index 11830c294116d..84b779466dbf9 100644 --- a/core/java/android/content/pm/PackageItemInfo.java +++ b/core/java/android/content/pm/PackageItemInfo.java @@ -42,6 +42,9 @@ */ public class PackageItemInfo { private static final float MAX_LABEL_SIZE_PX = 500f; + /** The maximum length of a safe label, in characters */ + private static final int MAX_SAFE_LABEL_LENGTH = 50000; + /** * Public name of this item. From the "android:name" attribute. */ @@ -169,7 +172,8 @@ public CharSequence loadLabel(PackageManager pm) { // If the label contains new line characters it may push the UI // down to hide a part of it. Labels shouldn't have new line // characters, so just truncate at the first time one is seen. - final int labelLength = labelStr.length(); + final int labelLength = Math.min(labelStr.length(), MAX_SAFE_LABEL_LENGTH); + final StringBuffer sb = new StringBuffer(labelLength); int offset = 0; while (offset < labelLength) { final int codePoint = labelStr.codePointAt(offset); @@ -181,14 +185,19 @@ public CharSequence loadLabel(PackageManager pm) { break; } // replace all non-break space to " " in order to be trimmed + final int charCount = Character.charCount(codePoint); if (type == Character.SPACE_SEPARATOR) { - labelStr = labelStr.substring(0, offset) + " " + labelStr.substring(offset + - Character.charCount(codePoint)); + sb.append(' '); + } else { + sb.append(labelStr.charAt(offset)); + if (charCount == 2) { + sb.append(labelStr.charAt(offset + 1)); + } } - offset += Character.charCount(codePoint); + offset += charCount; } - labelStr = labelStr.trim(); + labelStr = sb.toString().trim(); if (labelStr.isEmpty()) { return packageName; } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index d7ecc81ffdba1..467eb9b0b0bfc 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -2504,6 +2504,28 @@ public boolean isActiveNetworkMetered() { } } + /** + * Returns if the active data network for the given UID is metered. A network + * is classified as metered when the user is sensitive to heavy data usage on + * that connection due to monetary costs, data limitations or + * battery/performance issues. You should check this before doing large + * data transfers, and warn the user or delay the operation until another + * network is available. + * + * @return {@code true} if large transfers should be avoided, otherwise + * {@code false}. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) + public boolean isActiveNetworkMeteredForUid(int uid) { + try { + return mService.isActiveNetworkMeteredForUid(uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * If the LockdownVpn mechanism is enabled, updates the vpn * with a reload of its profile. diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index a6fe7389bc723..f11372c2b31cb 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -66,6 +66,7 @@ interface IConnectivityManager NetworkQuotaInfo getActiveNetworkQuotaInfo(); boolean isActiveNetworkMetered(); + boolean isActiveNetworkMeteredForUid(int uid); boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress); diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 616cf436a9cb4..47f8ac6f1a88f 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -612,6 +612,11 @@ public InputConsumer createInputConsumer(Looper looper, String name, */ void notifyKeyguardTrustedChanged(); + /** + * The keyguard showing state has changed + */ + void onKeyguardShowingAndNotOccludedChanged(); + /** * Notifies the window manager that screen is being turned off. * diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index b7179ef948adf..025c551570663 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -459,7 +459,7 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) // The chunk must be at least the size of the string pool header. if (size < sizeof(ResStringPool_header)) { - LOG_ALWAYS_FATAL("Bad string block: data size %zu is too small to be a string block", size); + ALOGW("Bad string block: data size %zu is too small to be a string block", size); return (mError=BAD_TYPE); } @@ -469,7 +469,7 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) if (validate_chunk(reinterpret_cast(data), sizeof(ResStringPool_header), reinterpret_cast(data) + size, "ResStringPool_header") != NO_ERROR) { - LOG_ALWAYS_FATAL("Bad string block: malformed block dimensions"); + ALOGW("Bad string block: malformed block dimensions"); return (mError=BAD_TYPE); } @@ -6576,8 +6576,16 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, } } else if (ctype == RES_TABLE_LIBRARY_TYPE) { + if (group->dynamicRefTable.entries().size() == 0) { - status_t err = group->dynamicRefTable.load((const ResTable_lib_header*) chunk); + const ResTable_lib_header* lib = (const ResTable_lib_header*) chunk; + status_t err = validate_chunk(&lib->header, sizeof(*lib), + endPos, "ResTable_lib_header"); + if (err != NO_ERROR) { + return (mError=err); + } + + err = group->dynamicRefTable.load(lib); if (err != NO_ERROR) { return (mError=err); } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index a83b0919bee97..5766dfc76e6f3 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -971,7 +971,7 @@ private Network[] getVpnUnderlyingNetworks(int uid) { if (!mLockdownEnabled) { int user = UserHandle.getUserId(uid); synchronized (mVpns) { - Vpn vpn = mVpns.get(user); + Vpn vpn = getVpn(user); if (vpn != null && vpn.appliesToUid(uid)) { return vpn.getUnderlyingNetworks(); } @@ -1019,7 +1019,7 @@ private boolean isNetworkWithLinkPropertiesBlocked(LinkProperties lp, int uid, return false; } synchronized (mVpns) { - final Vpn vpn = mVpns.get(UserHandle.getUserId(uid)); + final Vpn vpn = getVpn(UserHandle.getUserId(uid)); if (vpn != null && vpn.isBlockingUid(uid)) { return true; } @@ -1096,7 +1096,7 @@ private Network getActiveNetworkForUidInternal(final int uid, boolean ignoreBloc final int user = UserHandle.getUserId(uid); int vpnNetId = NETID_UNSET; synchronized (mVpns) { - final Vpn vpn = mVpns.get(user); + final Vpn vpn = getVpn(user); if (vpn != null && vpn.appliesToUid(uid)) vpnNetId = vpn.getNetId(); } NetworkAgentInfo nai; @@ -1226,7 +1226,7 @@ public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) { if (!mLockdownEnabled) { synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn != null) { Network[] networks = vpn.getUnderlyingNetworks(); if (networks != null) { @@ -1341,7 +1341,17 @@ public NetworkQuotaInfo getActiveNetworkQuotaInfo() { public boolean isActiveNetworkMetered() { enforceAccessPermission(); - final int uid = Binder.getCallingUid(); + return isActiveNetworkMeteredCommon(Binder.getCallingUid()); + } + + @Override + public boolean isActiveNetworkMeteredForUid(int uid) { + enforceConnectivityInternalPermission(); + + return isActiveNetworkMeteredCommon(uid); + } + + private boolean isActiveNetworkMeteredCommon(int uid) { final NetworkCapabilities caps = getUnfilteredActiveNetworkState(uid).networkCapabilities; if (caps != null) { return !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); @@ -3450,7 +3460,7 @@ public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPacka throwIfLockdownEnabled(); synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn != null) { return vpn.prepare(oldPackage, newPackage); } else { @@ -3477,7 +3487,7 @@ public void setVpnPackageAuthorization(String packageName, int userId, boolean a enforceCrossUserPermission(userId); synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn != null) { vpn.setPackageAuthorization(packageName, authorized); } @@ -3496,7 +3506,7 @@ public ParcelFileDescriptor establishVpn(VpnConfig config) { throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { - return mVpns.get(user).establish(config); + return getVpn(user).establish(config); } } @@ -3513,7 +3523,7 @@ public void startLegacyVpn(VpnProfile profile) { } int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { - mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress); + getVpn(user).startLegacyVpn(profile, mKeyStore, egress); } } @@ -3527,7 +3537,7 @@ public LegacyVpnInfo getLegacyVpnInfo(int userId) { enforceCrossUserPermission(userId); synchronized (mVpns) { - return mVpns.get(userId).getLegacyVpnInfo(); + return getVpn(userId).getLegacyVpnInfo(); } } @@ -3591,7 +3601,7 @@ private VpnInfo createVpnInfo(Vpn vpn) { public VpnConfig getVpnConfig(int userId) { enforceCrossUserPermission(userId); synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn != null) { return vpn.getVpnConfig(); } else { @@ -3625,7 +3635,7 @@ public boolean updateLockdownVpn() { } int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { - Vpn vpn = mVpns.get(user); + Vpn vpn = getVpn(user); if (vpn == null) { Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown"); return false; @@ -3672,7 +3682,7 @@ private void throwIfLockdownEnabled() { */ private boolean startAlwaysOnVpn(int userId) { synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn == null) { // Shouldn't happen as all codepaths that point here should have checked the Vpn // exists already. @@ -3690,7 +3700,7 @@ public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) { enforceCrossUserPermission(userId); synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn == null) { Slog.w(TAG, "User " + userId + " has no Vpn configuration"); return false; @@ -3710,7 +3720,7 @@ public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean loc } synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn == null) { Slog.w(TAG, "User " + userId + " has no Vpn configuration"); return false; @@ -3732,7 +3742,7 @@ public String getAlwaysOnVpnPackage(int userId) { enforceCrossUserPermission(userId); synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); + Vpn vpn = getVpn(userId); if (vpn == null) { Slog.w(TAG, "User " + userId + " has no Vpn configuration"); return null; @@ -3878,22 +3888,38 @@ public void setAirplaneMode(boolean enable) { private void onUserStart(int userId) { synchronized (mVpns) { - Vpn userVpn = mVpns.get(userId); + Vpn userVpn = getVpn(userId); if (userVpn != null) { loge("Starting user already has a VPN"); return; } userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId); - mVpns.put(userId, userVpn); + setVpn(userId, userVpn); } if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { updateLockdownVpn(); } } + /** @hide */ + @VisibleForTesting + Vpn getVpn(int userId) { + synchronized (mVpns) { + return mVpns.get(userId); + } + } + + /** @hide */ + @VisibleForTesting + void setVpn(int userId, Vpn userVpn) { + synchronized (mVpns) { + mVpns.put(userId, userVpn); + } + } + private void onUserStop(int userId) { synchronized (mVpns) { - Vpn userVpn = mVpns.get(userId); + Vpn userVpn = getVpn(userId); if (userVpn == null) { loge("Stopped user has no VPN"); return; @@ -5467,7 +5493,7 @@ public boolean addVpnAddress(String address, int prefixLength) { throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { - return mVpns.get(user).addAddress(address, prefixLength); + return getVpn(user).addAddress(address, prefixLength); } } @@ -5476,7 +5502,7 @@ public boolean removeVpnAddress(String address, int prefixLength) { throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { - return mVpns.get(user).removeAddress(address, prefixLength); + return getVpn(user).removeAddress(address, prefixLength); } } @@ -5486,7 +5512,7 @@ public boolean setUnderlyingNetworksForVpn(Network[] networks) { int user = UserHandle.getUserId(Binder.getCallingUid()); boolean success; synchronized (mVpns) { - success = mVpns.get(user).setUnderlyingNetworks(networks); + success = getVpn(user).setUnderlyingNetworks(networks); } if (success) { notifyIfacesChangedForNetworkStats(); diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java index 78367fe97a540..4d5a920dd54f5 100644 --- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java +++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java @@ -112,10 +112,8 @@ private boolean updateConstraintsSatisfied(JobStatus jobStatus) { final boolean connected = (info != null) && info.isConnected(); final boolean connectionUsable = connected && validated; - final boolean metered = connected && (capabilities != null) - && !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); - final boolean unmetered = connected && (capabilities != null) - && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + final boolean metered = connected && mConnManager.isActiveNetworkMeteredForUid(jobUid); + final boolean unmetered = connected && !mConnManager.isActiveNetworkMeteredForUid(jobUid); final boolean notRoaming = connected && (info != null) && !info.isRoaming(); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 7bddcdda3a072..b3d7832e3bca8 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -2573,6 +2573,11 @@ public void onAppTransitionCancelledLocked(int transit) { public void onTrustedChanged() { mWindowManagerFuncs.notifyKeyguardTrustedChanged(); } + + @Override + public void onShowingChanged() { + mWindowManagerFuncs.onKeyguardShowingAndNotOccludedChanged(); + } }); String deviceKeyHandlerLib = mContext.getResources().getString( diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java index 941cd4441e23a..fd34c510d98da 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java @@ -86,6 +86,8 @@ public boolean hasLockscreenWallpaper() { @Override // Binder interface public void onShowingStateChanged(boolean showing) { mIsShowing = showing; + + mCallback.onShowingChanged(); } @Override // Binder interface @@ -119,6 +121,7 @@ public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) { public interface StateCallback { void onTrustedChanged(); + void onShowingChanged(); } public void dump(String prefix, PrintWriter pw) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index a9e56f34b0d64..5a75262a80233 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -165,10 +165,18 @@ class RootWindowContainer extends WindowContainer { } WindowState computeFocusedWindow() { + // While the keyguard is showing, we must focus anything besides the main display. + // Otherwise we risk input not going to the keyguard when the user expects it to. + final boolean forceDefaultDisplay = mService.mPolicy.isKeyguardShowingAndNotOccluded(); + for (int i = mChildren.size() - 1; i >= 0; i--) { final DisplayContent dc = mChildren.get(i); final WindowState win = dc.findFocusedWindow(); if (win != null) { + if (forceDefaultDisplay && !dc.isDefaultDisplay) { + EventLog.writeEvent(0x534e4554, "71786287", win.mOwnerUid, ""); + continue; + } return win; } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b4b7c4bed0de8..6c9d856f2723e 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2931,6 +2931,11 @@ public void notifyKeyguardTrustedChanged() { mH.sendEmptyMessage(H.NOTIFY_KEYGUARD_TRUSTED_CHANGED); } + @Override + public void onKeyguardShowingAndNotOccludedChanged() { + mH.sendEmptyMessage(H.RECOMPUTE_FOCUS); + } + @Override public void screenTurningOff(ScreenOffListener listener) { mTaskSnapshotController.screenTurningOff(listener); @@ -4926,6 +4931,7 @@ final class H extends android.os.Handler { public static final int NOTIFY_KEYGUARD_FLAGS_CHANGED = 56; public static final int NOTIFY_KEYGUARD_TRUSTED_CHANGED = 57; public static final int SET_HAS_OVERLAY_UI = 58; + public static final int RECOMPUTE_FOCUS = 61; /** * Used to denote that an integer field in a message will not be used. @@ -5392,6 +5398,13 @@ public void handleMessage(Message msg) { mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1); } break; + case RECOMPUTE_FOCUS: { + synchronized (mWindowMap) { + updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, + true /* updateInputWindows */); + } + } + break; } if (DEBUG_WINDOW_TRACE) { Slog.v(TAG_WM, "handleMessage: exit"); diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java index 10d241357ff49..a6cada7b42e2c 100644 --- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java @@ -313,6 +313,25 @@ public void testFocusedWindowMultipleDisplays() throws Exception { assertEquals(window1, sWm.mRoot.computeFocusedWindow()); } + @Test + public void testKeyguard_preventsSecondaryDisplayFocus() throws Exception { + final WindowState keyguard = createWindow(null, TYPE_STATUS_BAR, + sWm.getDefaultDisplayContentLocked(), "keyguard"); + assertEquals(keyguard, sWm.mRoot.computeFocusedWindow()); + + // Add a window to a second display, and it should be focused + final DisplayContent dc = createNewDisplay(); + final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win"); + assertEquals(win, sWm.mRoot.computeFocusedWindow()); + + ((TestWindowManagerPolicy)sWm.mPolicy).keyguardShowingAndNotOccluded = true; + try { + assertEquals(keyguard, sWm.mRoot.computeFocusedWindow()); + } finally { + ((TestWindowManagerPolicy)sWm.mPolicy).keyguardShowingAndNotOccluded = false; + } + } + /** * This tests setting the maximum ui width on a display. */ diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java index eca27eefb2cd3..27ea325b8d804 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -63,6 +63,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { private static WindowManagerService sWm = null; int rotationToReport = 0; + boolean keyguardShowingAndNotOccluded = false; private Runnable mRunnableWhenAddingSplashScreen; @@ -420,7 +421,7 @@ public void exitKeyguardSecurely(OnKeyguardExitResult callback) { @Override public boolean isKeyguardLocked() { - return false; + return keyguardShowingAndNotOccluded; } @Override @@ -440,7 +441,7 @@ public boolean isKeyguardTrustedLw() { @Override public boolean isKeyguardShowingAndNotOccluded() { - return false; + return keyguardShowingAndNotOccluded; } @Override diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 6674f20317b3f..504a2dbc362b7 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -22,6 +22,7 @@ import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA; import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.ConnectivityManager.TYPE_VPN; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.NetworkCapabilities.*; @@ -102,6 +103,7 @@ import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkMonitor; import com.android.server.connectivity.NetworkMonitor.CaptivePortalProbeResult; +import com.android.server.connectivity.Vpn; import com.android.server.net.NetworkPinner; import com.android.server.net.NetworkPolicyManagerInternal; @@ -333,6 +335,9 @@ private class MockNetworkAgent { case TRANSPORT_WIFI_AWARE: mScore = 20; break; + case TRANSPORT_VPN: + mScore = 0; + break; default: throw new UnsupportedOperationException("unimplemented network type"); } @@ -868,6 +873,8 @@ private static int transportToLegacyType(int transport) { return TYPE_WIFI; case TRANSPORT_CELLULAR: return TYPE_MOBILE; + case TRANSPORT_VPN: + return TYPE_VPN; default: return TYPE_NONE; } @@ -3447,4 +3454,84 @@ private static void assertException(Runnable block, Class expected) { return; } } + + @SmallTest + public void testVpnNetworkMetered() { + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(callback); + + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + final TestNetworkCallback cellCallback = new TestNetworkCallback(); + mCm.registerNetworkCallback(cellRequest, cellCallback); + + // Setup cellular + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + verifyActiveNetwork(TRANSPORT_CELLULAR); + + // Verify meteredness of cellular + assertTrue(mCm.isActiveNetworkMetered()); + + // Setup Wifi + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + callback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + cellCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + verifyActiveNetwork(TRANSPORT_WIFI); + + // Verify meteredness of WiFi + assertTrue(mCm.isActiveNetworkMetered()); + + // Verify that setting unmetered on Wifi changes ActiveNetworkMetered + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + callback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent); + assertFalse(mCm.isActiveNetworkMetered()); + + // Setup VPN + final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); + vpnNetworkAgent.connect(true); + + Vpn mockVpn = mock(Vpn.class); + when(mockVpn.appliesToUid(anyInt())).thenReturn(true); + when(mockVpn.getNetId()).thenReturn(vpnNetworkAgent.getNetwork().netId); + + Vpn oldVpn = mService.getVpn(UserHandle.myUserId()); + mService.setVpn(UserHandle.myUserId(), mockVpn); + assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + // Verify meteredness of VPN on default network + when(mockVpn.getUnderlyingNetworks()).thenReturn(null); + assertFalse(mCm.isActiveNetworkMetered()); + assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + + // Verify meteredness of VPN on unmetered wifi + when(mockVpn.getUnderlyingNetworks()) + .thenReturn(new Network[] {mWiFiNetworkAgent.getNetwork()}); + assertFalse(mCm.isActiveNetworkMetered()); + assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + + // Set WiFi as metered, then check to see that it has been updated on the VPN + mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); + callback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent); + assertTrue(mCm.isActiveNetworkMetered()); + assertTrue(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + + // Switch to cellular + when(mockVpn.getUnderlyingNetworks()) + .thenReturn(new Network[] {mCellNetworkAgent.getNetwork()}); + assertTrue(mCm.isActiveNetworkMetered()); + assertTrue(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + + // Test unmetered cellular + mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + cellCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent); + assertFalse(mCm.isActiveNetworkMetered()); + assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + + mService.setVpn(UserHandle.myUserId(), oldVpn); + mCm.unregisterNetworkCallback(callback); + } }