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/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/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index c297e5a78d244..de1c96cf5ee27 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -1741,7 +1741,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 56cba795355ef..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); } @@ -376,6 +378,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,24 +391,166 @@ 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, wrap(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); - + 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 @@ -434,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); } @@ -472,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 @@ -645,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 + ")"; + } + } } 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); 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/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!"); + } + } /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c0fe253e85d8c..92c2299609551 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). @@ -3765,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 @@ -4790,12 +4828,26 @@ 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 */ 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. @@ -8280,6 +8332,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/text/Layout.java b/core/java/android/text/Layout.java index a233ba118e7dc..84ef9435f64ce 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,47 @@ 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 || offset < mLineStartOffset + || offset >= mLineStartOffset + mHorizontals.length) { + 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. * 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..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. * @@ -1753,4 +1758,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/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/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/AndroidManifest.xml b/core/res/AndroidManifest.xml index b589cb2ff0877..2692c5cd5d457 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3938,6 +3938,9 @@ + + diff --git a/core/res/res/values/aos_config.xml b/core/res/res/values/aos_config.xml index 2b24ffae9c62d..506007808fca0 100644 --- a/core/res/res/values/aos_config.xml +++ b/core/res/res/values/aos_config.xml @@ -135,4 +135,13 @@ false false false + + + 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/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/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/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)); diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index e790c0f26bbfa..2862e02c46b11 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -221,6 +221,9 @@ + + + + + + + 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/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/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"> @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/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/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 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 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/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java index b2a80f4ca52ca..4a6786832df01 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java @@ -16,6 +16,8 @@ package com.android.systemui.media; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; @@ -36,6 +38,7 @@ import android.text.TextUtils; import android.text.style.StyleSpan; import android.util.Log; +import android.view.Window; import android.view.WindowManager; import android.widget.CheckBox; import android.widget.CompoundButton; @@ -146,7 +149,9 @@ public void onCreate(Bundle icicle) { mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true); ((CheckBox) mDialog.findViewById(R.id.remember)).setOnCheckedChangeListener(this); - mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + final Window w = mDialog.getWindow(); + w.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + w.addPrivateFlags(PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); mDialog.show(); } 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/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; 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..f9cb2efafd38e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SmartPixelsTile.java @@ -0,0 +1,153 @@ +/* + * 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) { + 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(); + } + + @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(); + } + }; +} 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/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; + } } /** 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..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; @@ -271,6 +272,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 +543,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 +1130,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 +1531,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 +6545,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 +8742,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/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 993ec84731e80..f5a9a5b6b0c35 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -212,9 +212,7 @@ private void initDialog() { lp.type = mWindowType; lp.format = PixelFormat.TRANSLUCENT; lp.setTitle(VolumeDialogImpl.class.getSimpleName()); - lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; - lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top); - lp.gravity = Gravity.TOP; + lp.gravity = Gravity.TOP | Gravity.END; lp.windowAnimations = -1; mWindow.setAttributes(lp); mWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); @@ -302,15 +300,7 @@ private ColorStateList loadColorStateList(int colorResId) { private void updateWindowWidthH() { final ViewGroup.LayoutParams lp = mDialogView.getLayoutParams(); - final DisplayMetrics dm = mContext.getResources().getDisplayMetrics(); - if (D.BUG) Log.d(TAG, "updateWindowWidth dm.w=" + dm.widthPixels); - int w = dm.widthPixels; - final int max = mContext.getResources() - .getDimensionPixelSize(R.dimen.volume_dialog_panel_width); - if (w > max) { - w = max; - } - lp.width = w; + lp.width = ViewGroup.LayoutParams.WRAP_CONTENT; mDialogView.setLayoutParams(lp); } @@ -593,12 +583,12 @@ private void updateExpandedH(final boolean expanded, final boolean dismissing) { if (D.BUG) Log.d(TAG, "updateExpandedH " + expanded); updateExpandButtonH(); updateFooterH(); - TransitionManager.endTransitions(mDialogView); + //TransitionManager.endTransitions(mDialogView); final VolumeRow activeRow = getActiveRow(); - if (!dismissing) { - mWindow.setLayout(mWindow.getAttributes().width, ViewGroup.LayoutParams.MATCH_PARENT); - TransitionManager.beginDelayedTransition(mDialogView, getTransition()); - } + //if (!dismissing) { + // mWindow.setLayout(mWindow.getAttributes().width, ViewGroup.LayoutParams.MATCH_PARENT); + // TransitionManager.beginDelayedTransition(mDialogView, getTransition()); + //} updateRowsH(activeRow); rescheduleTimeoutH(); } @@ -726,8 +716,8 @@ private void updateFooterH() { && (mAudioManager.isStreamAffectedByRingerMode(mActiveStream) || mExpanded) && !mZenPanel.isEditing(); - TransitionManager.endTransitions(mDialogView); - TransitionManager.beginDelayedTransition(mDialogView, getTransition()); + //TransitionManager.endTransitions(mDialogView); + //TransitionManager.beginDelayedTransition(mDialogView, getTransition()); if (wasVisible != visible && !visible) { prepareForCollapse(); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java index 01d31e2a98521..c36a8c2f37576 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java @@ -69,7 +69,6 @@ public void onDismiss(DialogInterface dialog) { public void onShow(DialogInterface dialog) { if (D.BUG) Log.d(TAG, "mDialog.onShow"); final int h = mDialogView.getHeight(); - mDialogView.setTranslationY(-h); startShowAnimation(); } }); @@ -129,7 +128,7 @@ private int chevronPosY() { private void startShowAnimation() { if (D.BUG) Log.d(TAG, "startShowAnimation"); mDialogView.animate() - .translationY(0) + .translationX(0) .setDuration(scaledDuration(300)) .setInterpolator(new LogDecelerateInterpolator()) .setListener(null) @@ -174,7 +173,7 @@ public void onAnimationCancel(Animator animation) { @Override public void onAnimationUpdate(ValueAnimator animation) { float v = (Float) animation.getAnimatedValue(); - mContents.setTranslationY(v + -mDialogView.getTranslationY()); + //mContents.setTranslationY(v + -mDialogView.getTranslationY()); } }); mContentsPositionAnimator.setInterpolator(new LogDecelerateInterpolator()); @@ -218,13 +217,13 @@ public void startDismiss(final Runnable onComplete) { setShowing(false); } mDialogView.animate() - .translationY(-mDialogView.getHeight()) + .translationX(mDialogView.getHeight()) .setDuration(scaledDuration(250)) .setInterpolator(new LogAccelerateInterpolator()) .setUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { - mContents.setTranslationY(-mDialogView.getTranslationY()); + //mContents.setTranslationY(-mDialogView.getTranslationY()); final int posY = chevronPosY(); mChevron.setTranslationY(posY + -mDialogView.getTranslationY()); } 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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index f84b12986e92a..a4b35bf6d2377 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -8850,10 +8850,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) { @@ -8862,7 +8869,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? @@ -8878,7 +8885,7 @@ int checkGrantUriPermissionLocked(int callingUid, String targetPkg, GrantUri gra } } if (allowed) { - return -1; + return allowedResult; } } 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); } 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); 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/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 97ef559ec78b2..0dc4e837e381a 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -132,6 +132,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; @@ -1535,6 +1536,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); @@ -1682,6 +1701,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) { @@ -1733,6 +1753,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) { @@ -1813,6 +1834,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) { @@ -1872,6 +1894,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) { @@ -1893,6 +1916,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) { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 9ed35690dec15..b3d7832e3bca8 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) { @@ -2504,6 +2573,11 @@ public void onAppTransitionCancelledLocked(int transit) { public void onTrustedChanged() { mWindowManagerFuncs.notifyKeyguardTrustedChanged(); } + + @Override + public void onShowingChanged() { + mWindowManagerFuncs.onKeyguardShowingAndNotOccludedChanged(); + } }); String deviceKeyHandlerLib = mContext.getResources().getString( @@ -2746,6 +2820,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 +2896,7 @@ public void updateSettings() { if (updateRotation) { updateRotation(true); } + } private void updateNavigationBarSize() { @@ -4798,6 +4886,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 +6277,7 @@ public int finishPostLayoutPolicyLw() { // update since mAllowLockscreenWhenOn might have changed updateLockScreenTimeout(); + updateEdgeGestureListenerState(); return changes; } @@ -8245,6 +8336,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/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/smartpixels/Grids.java b/services/core/java/com/android/server/smartpixels/Grids.java new file mode 100644 index 0000000000000..b08e61bff7400 --- /dev/null +++ b/services/core/java/com/android/server/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.server.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/services/core/java/com/android/server/smartpixels/SmartPixelsReceiver.java b/services/core/java/com/android/server/smartpixels/SmartPixelsReceiver.java new file mode 100644 index 0000000000000..27172a5d12ba2 --- /dev/null +++ b/services/core/java/com/android/server/smartpixels/SmartPixelsReceiver.java @@ -0,0 +1,141 @@ +/* + * 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.server.smartpixels; + +import android.content.BroadcastReceiver; +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; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; + +// Handles Runtime start, PowerSave, and Settings changes +public class SmartPixelsReceiver extends BroadcastReceiver { + private static final String TAG = "SmartPixelsReceiver"; + + private Context mContext; + private Handler mHandler; + private ContentResolver mResolver; + private final PowerManager mPowerManager; + private SettingsObserver mSettingsObserver; + private Intent mSmartPixelsService; + + private boolean mSmartPixelsEnable; + private boolean mSmartPixelsOnPowerSave; + private boolean mPowerSaveEnable; + private boolean mSmartPixelsRunning = false; + + 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); + + registerReceiver(); + initiateSettingsObserver(); + } + + 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); + } + + private void initiateSettingsObserver() { + mSettingsObserver = new SettingsObserver(mHandler); + mSettingsObserver.observe(); + mSettingsObserver.update(); + } + + 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 new file mode 100644 index 0000000000000..5efe51e5dcc2f --- /dev/null +++ b/services/core/java/com/android/server/smartpixels/SmartPixelsService.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2015, Sergii Pylypenko + * (c) 2018, Joe Maples + * (c) 2018, Adin Kwok + * (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.server.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.PowerManager; +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; + +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; + 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(); + 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(); + } + 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) { + 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(com.android.internal.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/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..edd52461cdfd2 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -49,6 +49,7 @@ import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; @@ -1987,6 +1988,11 @@ public int relayoutWindow(Session session, IWindow client, int seq, // No move or resize, but the controller checks for title changes as well mAccessibilityController.onSomeWindowResizedOrMovedLocked(); } + + if ((flagChanges & PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) { + updateNonSystemOverlayWindowsVisibilityIfNeeded( + win, win.mWinAnimator.getShown()); + } } if (DEBUG_LAYOUT) Slog.v(TAG_WM, "Relayout " + win + ": viewVisibility=" + viewVisibility @@ -2931,6 +2937,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 +4937,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 +5404,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"); @@ -7717,7 +7736,8 @@ boolean hasWideColorGamutSupport() { } void updateNonSystemOverlayWindowsVisibilityIfNeeded(WindowState win, boolean surfaceShown) { - if (!win.hideNonSystemOverlayWindowsWhenVisible()) { + if (!win.hideNonSystemOverlayWindowsWhenVisible() + && !mHidingNonSystemOverlayWindows.contains(win)) { return; } final boolean systemAlertWindowsHidden = !mHidingNonSystemOverlayWindows.isEmpty(); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index fe9ad0d044deb..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; @@ -104,6 +105,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 +244,7 @@ public final class SystemServer { private PackageManager mPackageManager; private ContentResolver mContentResolver; private EntropyMixer mEntropyMixer; + private SmartPixelsReceiver mSmartPixelsReceiver; private boolean mOnlyCore; private boolean mFirstBoot; @@ -907,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) { @@ -1538,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) { @@ -1685,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 { @@ -1900,6 +1917,7 @@ private void startOtherServices() { reportWtf("Notifying incident daemon running", e); } traceEnd(); + mSmartPixelsReceiver = new SmartPixelsReceiver(context); }, BOOT_TIMINGS_TRACE_LOG); } 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 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); + } }