diff --git a/CleanSpec.mk b/CleanSpec.mk index 02e8eecbb721..e8ab07d64603 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -78,6 +78,7 @@ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/libhwui.so) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libhwui.so) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os/storage/*) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/content/IClipboard.P) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/pocket/*) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/telephony/java/com/android/internal/telephony/ITelephonyRegistry.P) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/docs/api-stubs*) diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index c9031b711657..4d4aa4a381f8 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -101,6 +101,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.Immutable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.gmscompat.AttestationHooks; import com.android.internal.os.SomeArgs; import com.android.internal.util.UserIcons; @@ -668,7 +669,8 @@ protected Boolean recompute(HasSystemFeatureQuery query) { @Override public boolean hasSystemFeature(String name, int version) { - return mHasSystemFeatureCache.query(new HasSystemFeatureQuery(name, version)); + return AttestationHooks.hasSystemFeature(name, + mHasSystemFeatureCache.query(new HasSystemFeatureQuery(name, version))); } /** @hide */ diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 721525d9af9d..4b7d1ef6f342 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -56,6 +56,7 @@ import android.view.WindowManagerGlobal; import com.android.internal.content.ReferrerIntent; +import com.android.internal.gmscompat.AttestationHooks; import java.io.File; import java.lang.annotation.Retention; @@ -1157,6 +1158,7 @@ public Application newApplication(ClassLoader cl, String className, Context cont Application app = getFactory(context.getPackageName()) .instantiateApplication(cl, className); app.attach(context); + AttestationHooks.initApplicationBeforeOnCreate(app); return app; } @@ -1174,6 +1176,7 @@ static public Application newApplication(Class clazz, Context context) ClassNotFoundException { Application app = (Application)clazz.newInstance(); app.attach(context); + AttestationHooks.initApplicationBeforeOnCreate(app); return app; } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 5e0913aaa30e..0dc4a0610120 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -173,6 +173,8 @@ import android.os.storage.StorageManager; import android.permission.PermissionControllerManager; import android.permission.PermissionManager; +import android.pocket.IPocketService; +import android.pocket.PocketManager; import android.print.IPrintManager; import android.print.PrintManager; import android.security.FileIntegrityManager; @@ -943,6 +945,15 @@ public DcDimmingManager createService(ContextImpl ctx) throws ServiceNotFoundExc return new DcDimmingManager(service); }}); + registerService(Context.POCKET_SERVICE, PocketManager.class, + new CachedServiceFetcher() { + @Override + public PocketManager createService(ContextImpl ctx) { + IBinder binder = ServiceManager.getService(Context.POCKET_SERVICE); + IPocketService service = IPocketService.Stub.asInterface(binder); + return new PocketManager(ctx.getOuterContext(), service); + }}); + registerService(Context.TV_INPUT_SERVICE, TvInputManager.class, new CachedServiceFetcher() { @Override diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index e0d2d45f125e..de289df53ca7 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -5086,6 +5086,16 @@ public abstract boolean startInstrumentation(@NonNull ComponentName className, */ public static final String DYNAMIC_SYSTEM_SERVICE = "dynamic_system"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.os.PocketManager} for accessing and listening to device pocket state. + * + * @hide + * @see #getSystemService + * @see android.os.PocketManager + */ + public static final String POCKET_SERVICE = "pocket"; + /** * Use with {@link #getSystemService(String)} to retrieve a {@link * android.app.blob.BlobStoreManager} for contributing and accessing data blobs diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index 27016e8e3f0f..2b8d3b63c12e 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -21,6 +21,7 @@ import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; @@ -777,6 +778,13 @@ private ParseResult parseBaseApkTags(ParseInput input, ParsingPa ); } + if (ParsedPermissionUtils.declareDuplicatePermission(pkg)) { + return input.error( + INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + "Found duplicate permission with a different attribute value." + ); + } + convertNewPermissions(pkg); convertSplitPermissions(pkg); diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java index 1884a1e27832..fa7cfceb1a4c 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java @@ -22,6 +22,8 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.util.ArrayMap; +import android.util.EventLog; import android.util.Slog; import com.android.internal.R; @@ -32,6 +34,8 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.util.List; +import java.util.Objects; /** @hide */ public class ParsedPermissionUtils { @@ -207,4 +211,48 @@ public static ParseResult parsePermissionGroup(ParsingPac return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permissionGroup, input); } + + /** + * Determines if a duplicate permission is malformed .i.e. defines different protection level + * or group. + */ + private static boolean isMalformedDuplicate(ParsedPermission p1, ParsedPermission p2) { + // Since a permission tree is also added as a permission with normal protection + // level, we need to skip if the parsedPermission is a permission tree. + if (p1 == null || p2 == null || p1.isTree() || p2.isTree()) { + return false; + } + + if (p1.getProtectionLevel() != p2.getProtectionLevel()) { + return true; + } + if (!Objects.equals(p1.getGroup(), p2.getGroup())) { + return true; + } + + return false; + } + + /** + * @return {@code true} if the package declares malformed duplicate permissions. + */ + public static boolean declareDuplicatePermission(@NonNull ParsingPackage pkg) { + final List permissions = pkg.getPermissions(); + final int size = permissions.size(); + if (size > 0) { + final ArrayMap checkDuplicatePerm = new ArrayMap<>(size); + for (int i = 0; i < size; i++) { + final ParsedPermission parsedPermission = permissions.get(i); + final String name = parsedPermission.getName(); + final ParsedPermission perm = checkDuplicatePerm.get(name); + if (isMalformedDuplicate(parsedPermission, perm)) { + // Fix for b/213323615 + EventLog.writeEvent(0x534e4554, "213323615"); + return true; + } + checkDuplicatePerm.put(name, parsedPermission); + } + } + return false; + } } diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index 0cce19222d27..2bbff4b5002a 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -323,6 +323,7 @@ interface INetworkManagementService void setFirewallInterfaceRule(String iface, boolean allow); void setFirewallUidRule(int chain, int uid, int rule); void setFirewallUidRules(int chain, in int[] uids, in int[] rules); + void setFirewallMACAddressRule(String macAddr, boolean allow); void setFirewallChainEnabled(int chain, boolean enable); /** diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index e5bab6fc9230..5a4b22b8a91b 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -443,6 +443,7 @@ public static Parcel obtain() { */ public final void recycle() { if (DEBUG_RECYCLE) mStack = null; + mClassCookies = null; freeBuffer(); final Parcel[] pool; diff --git a/core/java/android/pocket/IPocketCallback.aidl b/core/java/android/pocket/IPocketCallback.aidl new file mode 100644 index 000000000000..53e5412f89be --- /dev/null +++ b/core/java/android/pocket/IPocketCallback.aidl @@ -0,0 +1,24 @@ +/** + * Copyright (C) 2016 The ParanoidAndroid Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.pocket; + +/** @hide */ +interface IPocketCallback { + + // notify when pocket state changes. + void onStateChanged(boolean isDeviceInPocket, int reason); + +} \ No newline at end of file diff --git a/core/java/android/pocket/IPocketService.aidl b/core/java/android/pocket/IPocketService.aidl new file mode 100644 index 000000000000..783465774207 --- /dev/null +++ b/core/java/android/pocket/IPocketService.aidl @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2016 The ParanoidAndroid Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.pocket; + +import android.pocket.IPocketCallback; + +/** @hide */ +interface IPocketService { + + // add callback to get notified about pocket state. + void addCallback(IPocketCallback callback); + + // remove callback and stop getting notified about pocket state. + void removeCallback(IPocketCallback callback); + + // notify pocket service about intercative state changed. + // @see com.android.policy.PhoneWindowManager + void onInteractiveChanged(boolean interactive); + + // external processes can request changing listening state. + void setListeningExternal(boolean listen); + + // check if device is in pocket. + boolean isDeviceInPocket(); + + // Custom methods + void setPocketLockVisible(boolean visible); + boolean isPocketLockVisible(); + +} \ No newline at end of file diff --git a/core/java/android/pocket/PocketConstants.java b/core/java/android/pocket/PocketConstants.java new file mode 100644 index 000000000000..70aa74a7f2a6 --- /dev/null +++ b/core/java/android/pocket/PocketConstants.java @@ -0,0 +1,19 @@ +package android.pocket; + +/** + * This class contains global pocket setup constants. + * @author Carlo Savignano + * @hide + */ + +public class PocketConstants { + + public static final boolean DEBUG = false; + public static final boolean DEBUG_SPEW = false; + + /** + * Whether to use proximity sensor to evaluate pocket state. + */ + public static final boolean ENABLE_PROXIMITY_JUDGE = true; + +} diff --git a/core/java/android/pocket/PocketManager.java b/core/java/android/pocket/PocketManager.java new file mode 100644 index 000000000000..22b60696289b --- /dev/null +++ b/core/java/android/pocket/PocketManager.java @@ -0,0 +1,233 @@ +/** + * Copyright (C) 2016 The ParanoidAndroid Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.pocket; + +import android.content.Context; +import android.os.Handler; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.SystemClock; +import android.text.format.DateUtils; +import android.util.Log; +import android.util.Slog; + +/** + * A class that coordinates listening for pocket state. + *

+ * Use {@link android.content.Context#getSystemService(java.lang.String)} + * with argument {@link android.content.Context#POCKET_SERVICE} to get + * an instance of this class. + * + * Usage: import and create a final {@link IPocketCallback.Stub()} and implement your logic in + * {@link IPocketCallback#onStateChanged(boolean, int)}. Then add your callback to the pocket manager + * + * // define a final callback + * private final IPocketCallback mCallback = new IPocketCallback.Stub() { + * + * @Override + * public void onStateChanged(boolean isDeviceInPocket, int reason) { + * // Your method to handle logic outside of this callback, ideally with a handler + * // posting on UI Thread for view hierarchy operations or with its own background thread. + * handlePocketStateChanged(isDeviceInPocket, reason); + * } + * + * } + * + * // add callback to pocket manager + * private void addCallback() { + * PocketManager manager = (PocketManager) context.getSystemService(Context.POCKET_SERVICE); + * manager.addCallback(mCallback); + * } + * + * @author Carlo Savignano + * @hide + */ +public class PocketManager { + + private static final String TAG = PocketManager.class.getSimpleName(); + static final boolean DEBUG = false; + + /** + * Whether {@link IPocketCallback#onStateChanged(boolean, int)} + * was fired because of the sensor. + * @see PocketService#handleDispatchCallbacks() + */ + public static final int REASON_SENSOR = 0; + + /** + * Whether {@link IPocketCallback#onStateChanged(boolean, int)} + * was fired because of an error while accessing service. + * @see #addCallback(IPocketCallback) + * @see #removeCallback(IPocketCallback) + */ + public static final int REASON_ERROR = 1; + + /** + * Whether {@link IPocketCallback#onStateChanged(boolean, int)} + * was fired because of a needed reset. + * @see PocketService#binderDied() + */ + public static final int REASON_RESET = 2; + + private Context mContext; + private IPocketService mService; + private PowerManager mPowerManager; + private Handler mHandler; + private boolean mPocketViewTimerActive; + + public PocketManager(Context context, IPocketService service) { + mContext = context; + mService = service; + if (mService == null) { + Slog.v(TAG, "PocketService was null"); + } + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mHandler = new Handler(); + } + + /** + * Add pocket state callback. + * @see PocketService#handleRemoveCallback(IPocketCallback) + */ + public void addCallback(final IPocketCallback callback) { + if (mService != null) try { + mService.addCallback(callback); + } catch (RemoteException e1) { + Log.w(TAG, "Remote exception in addCallback: ", e1); + if (callback != null){ + try { + callback.onStateChanged(false, REASON_ERROR); + } catch (RemoteException e2) { + Log.w(TAG, "Remote exception in callback.onPocketStateChanged: ", e2); + } + } + } + } + + /** + * Remove pocket state callback. + * @see PocketService#handleAddCallback(IPocketCallback) + */ + public void removeCallback(final IPocketCallback callback) { + if (mService != null) try { + mService.removeCallback(callback); + } catch (RemoteException e1) { + Log.w(TAG, "Remote exception in removeCallback: ", e1); + if (callback != null){ + try { + callback.onStateChanged(false, REASON_ERROR); + } catch (RemoteException e2) { + Log.w(TAG, "Remote exception in callback.onPocketStateChanged: ", e2); + } + } + } + } + + /** + * Notify service about device interactive state changed. + * {@link PhoneWindowManager#startedWakingUp()} + * {@link PhoneWindowManager#startedGoingToSleep(int)} + */ + public void onInteractiveChanged(boolean interactive) { + boolean isPocketViewShowing = (interactive && isDeviceInPocket()); + synchronized (mPocketLockTimeout) { + if (mPocketViewTimerActive != isPocketViewShowing) { + if (isPocketViewShowing) { + if (DEBUG) Log.v(TAG, "Setting pocket timer"); + mHandler.removeCallbacks(mPocketLockTimeout); // remove any pending requests + mHandler.postDelayed(mPocketLockTimeout, 3 * DateUtils.SECOND_IN_MILLIS); + mPocketViewTimerActive = true; + } else { + if (DEBUG) Log.v(TAG, "Clearing pocket timer"); + mHandler.removeCallbacks(mPocketLockTimeout); + mPocketViewTimerActive = false; + } + } + } + if (mService != null) try { + mService.onInteractiveChanged(interactive); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in addCallback: ", e); + } + } + + /** + * Request listening state change by, but not limited to, external process. + * @see PocketService#handleSetListeningExternal(boolean) + */ + public void setListeningExternal(boolean listen) { + if (mService != null) try { + mService.setListeningExternal(listen); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in setListeningExternal: ", e); + } + // Clear timeout when user hides pocket lock with long press power. + if (mPocketViewTimerActive && !listen) { + if (DEBUG) Log.v(TAG, "Clearing pocket timer due to override"); + mHandler.removeCallbacks(mPocketLockTimeout); + mPocketViewTimerActive = false; + } + } + + /** + * Return whether device is in pocket. + * @see PocketService#isDeviceInPocket() + * @return + */ + public boolean isDeviceInPocket() { + if (mService != null) try { + return mService.isDeviceInPocket(); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in isDeviceInPocket: ", e); + } + return false; + } + + class PocketLockTimeout implements Runnable { + @Override + public void run() { + mPowerManager.goToSleep(SystemClock.uptimeMillis()); + mPocketViewTimerActive = false; + } + } + + /** Custom methods **/ + + public void setPocketLockVisible(boolean visible) { + if (!visible){ + if (DEBUG) Log.v(TAG, "Clearing pocket timer"); + mHandler.removeCallbacks(mPocketLockTimeout); + mPocketViewTimerActive = false; + } + if (mService != null) try { + mService.setPocketLockVisible(visible); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in setPocketLockVisible: ", e); + } + } + + public boolean isPocketLockVisible() { + if (mService != null) try { + return mService.isPocketLockVisible(); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in isPocketLockVisible: ", e); + } + return false; + } + + private PocketLockTimeout mPocketLockTimeout = new PocketLockTimeout(); + +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a1d5dd1e31f0..089c45c01a70 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4703,6 +4703,17 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean */ public static final String FOD_ANIM = "fod_recognizing_animation_list"; + /** + * Wheter to show network traffic indicator in statusbar + * @hide + */ + public static final String NETWORK_TRAFFIC_STATE = "network_traffic_state"; + /** + * Network traffic inactivity threshold (default is 1 kBs) + * @hide + */ + public static final String NETWORK_TRAFFIC_AUTOHIDE_THRESHOLD = "network_traffic_autohide_threshold"; + /** * Show pointer location on screen? * 0 = no @@ -5578,7 +5589,15 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean * 1 - Right of clock * @hide */ - public static final String STATUSBAR_CLOCK_DATE_POSITION = "statusbar_clock_date_position"; + public static final String STATUSBAR_CLOCK_DATE_POSITION = "statusbar_clock_date_position"; + + /** + * Statusbar clock background + * 0 - hide accented chip + * 1 - show accented chip (default) + * @hide + */ + public static final String STATUSBAR_CLOCK_CHIP = "statusbar_clock_chip"; /** * Whether to show the notification ticker on the status bar @@ -5733,6 +5752,15 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean */ public static final String QS_SHOW_BATTERY_PERCENT = "qs_header_show_battery_percent"; + /** + * Whether allowing pocket service to register sensors and dispatch informations. + * 0 = disabled + * 1 = enabled + * @author Carlo Savignano + * @hide + */ + public static final String POCKET_JUDGE = "pocket_judge"; + /** * Battery style * @hide @@ -5940,11 +5968,13 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean PRIVATE_SETTINGS.add(STATUSBAR_CLOCK_DATE_STYLE); PRIVATE_SETTINGS.add(STATUSBAR_CLOCK_DATE_FORMAT); PRIVATE_SETTINGS.add(STATUSBAR_CLOCK_DATE_POSITION); + PRIVATE_SETTINGS.add(STATUSBAR_CLOCK_CHIP); PRIVATE_SETTINGS.add(ACCENT_COLOR); PRIVATE_SETTINGS.add(USE_WALL_ACCENT); PRIVATE_SETTINGS.add(AUTO_ACCENT_TYPE); PRIVATE_SETTINGS.add(SYSTEMUI_PLUGIN_VOLUME); PRIVATE_SETTINGS.add(QS_SHOW_BATTERY_PERCENT); + PRIVATE_SETTINGS.add(POCKET_JUDGE); } /** @@ -10272,6 +10302,12 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val */ public static final String SWAP_CAPACITIVE_KEYS = "swap_capacitive_keys"; + /** + * Control whether FLAG_SECURE is ignored for all windows. + * @hide + */ + public static final String WINDOW_IGNORE_SECURE = "window_ignore_secure"; + /** * These entries are considered common between the personal and the managed profile, * since the managed profile doesn't get to change them. @@ -15713,6 +15749,12 @@ public static boolean putFloat(ContentResolver cr, String name, float value) { */ public static final String NR_NSA_TRACKING_SCREEN_OFF_MODE = "nr_nsa_tracking_screen_off_mode"; + + /** + * Control whether application downgrade is allowed. + * @hide + */ + public static final String PM_DOWNGRADE_ALLOWED = "pm_downgrade_allowed"; } /** diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index ca424e79ed7b..e3a220706b0b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3843,6 +3843,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @Deprecated public static final int SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR = 0x00000010; + /** + * @hide + * + * Pocket lock + */ + public static final int SYSTEM_UI_FLAG_POCKET_LOCK = 0x00000020; + /** * @deprecated Use {@link #SYSTEM_UI_FLAG_LOW_PROFILE} instead. */ diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index b399691ca787..3b86a6c9b8b1 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -17,6 +17,7 @@ package android.view; import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; +import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import android.annotation.ColorInt; import android.annotation.DrawableRes; @@ -45,6 +46,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.provider.Settings; import android.transition.Scene; import android.transition.Transition; import android.transition.TransitionManager; @@ -1191,6 +1193,10 @@ public void clearPrivateFlags(int flags) { * @see #clearFlags */ public void setFlags(int flags, int mask) { + if ((mask & FLAG_SECURE) != 0 && Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.WINDOW_IGNORE_SECURE, 0) == 1) { + mask &= ~FLAG_SECURE; + } final WindowManager.LayoutParams attrs = getAttributes(); attrs.flags = (attrs.flags&~mask) | (flags&mask); mForcedWindowFlags |= mask; diff --git a/core/java/com/android/internal/gmscompat/AttestationHooks.java b/core/java/com/android/internal/gmscompat/AttestationHooks.java new file mode 100644 index 000000000000..041b41831b73 --- /dev/null +++ b/core/java/com/android/internal/gmscompat/AttestationHooks.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.gmscompat; + +import android.app.Application; +import android.content.res.Resources; +import android.os.Build; +import android.os.SystemProperties; +import android.util.Log; + +import com.android.internal.R; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** @hide */ +public final class AttestationHooks { + private static final String TAG = "GmsCompat/Attestation"; + + private static final String PACKAGE_GMS = "com.google.android.gms"; + private static final String PACKAGE_GPHOTOS = "com.google.android.apps.photos"; + + private static final String PROCESS_UNSTABLE = "com.google.android.gms.unstable"; + + private static final String PRODUCT_GMS_SPOOFING_FINGERPRINT = + SystemProperties.get("ro.build.gms_fingerprint"); + + private static final Map sP1Props = new HashMap<>(); + static { + sP1Props.put("BRAND", "google"); + sP1Props.put("MANUFACTURER", "Google"); + sP1Props.put("DEVICE", "marlin"); + sP1Props.put("PRODUCT", "marlin"); + sP1Props.put("MODEL", "Pixel XL"); + sP1Props.put("FINGERPRINT", "google/marlin/marlin:10/QP1A.191005.007.A3/5972272:user/release-keys"); + } + + private static final String[] sFeaturesBlacklist = { + "PIXEL_2017_EXPERIENCE", + "PIXEL_2017_PRELOAD", + "PIXEL_2018_PRELOAD", + "PIXEL_2019_EXPERIENCE", + "PIXEL_2019_MIDYEAR_EXPERIENCE", + "PIXEL_2019_MIDYEAR_PRELOAD", + "PIXEL_2019_PRELOAD", + "PIXEL_2020_EXPERIENCE", + "PIXEL_2020_MIDYEAR_EXPERIENCE", + "PIXEL_2021_EXPERIENCE", + "PIXEL_2021_MIDYEAR_EXPERIENCE" + }; + + private static volatile boolean sIsPhotos = false; + + private static final boolean sSpoofPhotos = + Resources.getSystem().getBoolean(R.bool.config_spoofGooglePhotos); + + private AttestationHooks() { } + + private static void setBuildField(String key, String value) { + try { + // Unlock + Field field = Build.class.getDeclaredField(key); + field.setAccessible(true); + + // Edit + field.set(null, value); + + // Lock + field.setAccessible(false); + } catch (NoSuchFieldException | IllegalAccessException e) { + Log.e(TAG, "Failed to spoof Build." + key, e); + } + } + + private static void spoofBuildGms() { + // Set fingerprint for SafetyNet CTS profile + if (PRODUCT_GMS_SPOOFING_FINGERPRINT.length() > 0) { + setBuildField("FINGERPRINT", PRODUCT_GMS_SPOOFING_FINGERPRINT); + } + + // Alter model name to avoid hardware attestation enforcement + setBuildField("MODEL", Build.MODEL + " "); + } + + public static void initApplicationBeforeOnCreate(Application app) { + if (PACKAGE_GMS.equals(app.getPackageName()) && + PROCESS_UNSTABLE.equals(Application.getProcessName())) { + spoofBuildGms(); + } else if (sSpoofPhotos && PACKAGE_GPHOTOS.equals(app.getPackageName())) { + sIsPhotos = true; + sP1Props.forEach((k, v) -> setBuildField(k, v)); + } + } + + public static boolean hasSystemFeature(String name, boolean def) { + if (sIsPhotos && def && + Arrays.stream(sFeaturesBlacklist).anyMatch(name::contains)) { + return false; + } + return def; + } +} diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 29bc2886a179..ca7cec40b047 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -47,8 +47,6 @@ import java.io.IOException; import java.io.InputStreamReader; -import com.android.internal.util.custom.PixelPropsUtils; - /** @hide */ public final class Zygote { /* @@ -796,9 +794,6 @@ static void setAppProcessName(ZygoteArguments args, String loggingTag) { } else { Log.w(loggingTag, "Unable to set package name."); } - - // Set pixel props - PixelPropsUtils.setProps(args.mPackageName); } private static final String USAP_ERROR_PREFIX = "Invalid command to USAP: "; diff --git a/core/java/com/android/internal/util/custom/PixelPropsUtils.java b/core/java/com/android/internal/util/custom/PixelPropsUtils.java deleted file mode 100644 index 35e5bcc2694c..000000000000 --- a/core/java/com/android/internal/util/custom/PixelPropsUtils.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2020 The Pixel Experience Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.util.custom; - -import android.os.Build; -import android.util.Log; - -import java.util.Arrays; -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; - -public class PixelPropsUtils { - - private static final String TAG = PixelPropsUtils.class.getSimpleName(); - private static final boolean DEBUG = false; - - private static final Map propsToChangePixelXL; - private static final Map propsToChangePixel2; - private static final Map propsToChangePixel3XL; - private static final Map propsToChangePixel5a; - private static final Map propsToChangeOnePlus9Pro; - - private static final String[] packagesToChangePixelXL = { - "com.google.android.apps.photos", - "com.samsung.accessory.fridaymgr", - "com.samsung.accessory.berrymgr", - "com.samsung.accessory.neobeanmgr", - "com.samsung.android.app.watchmanager", - "com.samsung.android.geargplugin", - "com.samsung.android.gearnplugin", - "com.samsung.android.modenplugin", - "com.samsung.android.neatplugin", - "com.samsung.android.waterplugin" - }; - - private static final String[] packagesToChangePixel2 = { - "com.google.android.gms", - "com.google.android.gms.location.history" - }; - - private static final String[] packagesToChangePixel3XL = { - "com.google.android.googlequicksearchbox" - }; - - private static final String[] packagesToChangePixel5a = { - "com.breel.wallpapers20", - "com.google.android.apps.customization.pixel", - "com.google.android.apps.fitness", - "com.google.android.apps.maps", - "com.google.android.apps.recorder", - "com.google.android.apps.safetyhub", - "com.google.android.apps.subscriptions.red", - "com.google.android.apps.tachyon", - "com.google.android.apps.turbo", - "com.google.android.apps.turboadapter", - "com.google.android.apps.wallpaper", - "com.google.android.apps.wallpaper.pixel", - "com.google.android.as", - "com.google.android.dialer", - "com.google.android.inputmethod.latin", - "com.google.android.soundpicker", - "com.google.pixel.dynamicwallpapers", - "com.google.pixel.livewallpaper" - }; - - private static final String[] packagesToChangeOnePlus9Pro = { - "com.google.android.apps.wearables.maestro.companion" - }; - - static { - propsToChangePixelXL = new HashMap<>(); - propsToChangePixelXL.put("BRAND", "google"); - propsToChangePixelXL.put("MANUFACTURER", "Google"); - propsToChangePixelXL.put("DEVICE", "marlin"); - propsToChangePixelXL.put("PRODUCT", "marlin"); - propsToChangePixelXL.put("MODEL", "Pixel XL"); - propsToChangePixelXL.put("FINGERPRINT", "google/marlin/marlin:10/QP1A.191005.007.A3/5972272:user/release-keys"); - propsToChangePixel2 = new HashMap<>(); - propsToChangePixel2.put("BRAND", "google"); - propsToChangePixel2.put("MANUFACTURER", "Google"); - propsToChangePixel2.put("DEVICE", "walleye"); - propsToChangePixel2.put("PRODUCT", "walleye"); - propsToChangePixel2.put("MODEL", "Pixel 2"); - propsToChangePixel2.put("FINGERPRINT", "google/walleye/walleye:8.1.0/OPM1.171019.011/4448085:user/release-keys"); - propsToChangePixel3XL = new HashMap<>(); - propsToChangePixel3XL.put("BRAND", "google"); - propsToChangePixel3XL.put("MANUFACTURER", "Google"); - propsToChangePixel3XL.put("DEVICE", "crosshatch"); - propsToChangePixel3XL.put("PRODUCT", "crosshatch"); - propsToChangePixel3XL.put("MODEL", "Pixel 3 XL"); - propsToChangePixel3XL.put("FINGERPRINT", "google/crosshatch/crosshatch:11/RQ3A.210905.001/7511028:user/release-keys"); - propsToChangePixel5a = new HashMap<>(); - propsToChangePixel5a.put("BRAND", "google"); - propsToChangePixel5a.put("MANUFACTURER", "Google"); - propsToChangePixel5a.put("DEVICE", "barbet"); - propsToChangePixel5a.put("PRODUCT", "barbet"); - propsToChangePixel5a.put("MODEL", "Pixel 5a"); - propsToChangePixel5a.put("FINGERPRINT", "google/barbet/barbet:11/RD2A.210905.002/7513089:user/release-keys"); - propsToChangeOnePlus9Pro = new HashMap<>(); - propsToChangeOnePlus9Pro.put("BRAND", "OnePlus"); - propsToChangeOnePlus9Pro.put("MANUFACTURER", "OnePlus"); - propsToChangeOnePlus9Pro.put("DEVICE", "OnePlus9Pro"); - propsToChangeOnePlus9Pro.put("PRODUCT", "OnePlus9Pro_EEA"); - propsToChangeOnePlus9Pro.put("MODEL", "LE2123"); - propsToChangeOnePlus9Pro.put("FINGERPRINT", "OnePlus/OnePlus9Pro_EEA/OnePlus9Pro:11/RKQ1.201105.002/2107082109:user/release-keys"); - } - - public static void setProps(String packageName) { - if (packageName == null){ - return; - } - if (Arrays.asList(packagesToChangePixelXL).contains(packageName)){ - if (DEBUG){ - Log.d(TAG, "Defining props for: " + packageName); - } - for (Map.Entry prop : propsToChangePixelXL.entrySet()) { - String key = prop.getKey(); - Object value = prop.getValue(); - setPropValue(key, value); - } - } - if (Arrays.asList(packagesToChangePixel2).contains(packageName)){ - if (DEBUG){ - Log.d(TAG, "Defining props for: " + packageName); - } - for (Map.Entry prop : propsToChangePixel2.entrySet()) { - String key = prop.getKey(); - Object value = prop.getValue(); - setPropValue(key, value); - } - } - if (Arrays.asList(packagesToChangePixel3XL).contains(packageName)){ - if (DEBUG){ - Log.d(TAG, "Defining props for: " + packageName); - } - for (Map.Entry prop : propsToChangePixel3XL.entrySet()) { - String key = prop.getKey(); - Object value = prop.getValue(); - setPropValue(key, value); - } - } - if (Arrays.asList(packagesToChangePixel5a).contains(packageName)){ - if (DEBUG){ - Log.d(TAG, "Defining props for: " + packageName); - } - for (Map.Entry prop : propsToChangePixel5a.entrySet()) { - String key = prop.getKey(); - Object value = prop.getValue(); - setPropValue(key, value); - } - } - if (Arrays.asList(packagesToChangeOnePlus9Pro).contains(packageName)){ - if (DEBUG){ - Log.d(TAG, "Defining props for: " + packageName); - } - for (Map.Entry prop : propsToChangeOnePlus9Pro.entrySet()) { - String key = prop.getKey(); - Object value = prop.getValue(); - setPropValue(key, value); - } - } - } - - private static void setPropValue(String key, Object value){ - try { - if (DEBUG){ - Log.d(TAG, "Defining prop " + key + " to " + value.toString()); - } - Field field = Build.class.getDeclaredField(key); - field.setAccessible(true); - field.set(null, value); - field.setAccessible(false); - } catch (NoSuchFieldException | IllegalAccessException e) { - Log.e(TAG, "Failed to set prop " + key, e); - } - } -} \ No newline at end of file diff --git a/core/java/com/android/internal/util/custom/ThemeUtils.java b/core/java/com/android/internal/util/custom/ThemeUtils.java new file mode 100644 index 000000000000..20cedfb1a455 --- /dev/null +++ b/core/java/com/android/internal/util/custom/ThemeUtils.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util.custom; + +import static android.os.UserHandle.USER_SYSTEM; + +import android.util.PathParser; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.om.IOverlayManager; +import android.content.om.OverlayInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ProviderInfo; +import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; +import android.content.res.Configuration; +import android.database.Cursor; +import android.graphics.Typeface; +import android.graphics.Path; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.PathShape; +import android.net.Uri; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class ThemeUtils { + + public static final String TAG = "ThemeUtils"; + + public static final String FONT_KEY = "android.theme.customization.font"; + public static final String ICON_SHAPE_KEY= "android.theme.customization.adaptive_icon_shape"; + + public static final Comparator OVERLAY_INFO_COMPARATOR = + Comparator.comparingInt(a -> a.priority); + + private Context mContext; + private IOverlayManager mOverlayManager; + private PackageManager pm; + private Resources overlayRes; + + public ThemeUtils(Context context) { + mContext = context; + mOverlayManager = IOverlayManager.Stub + .asInterface(ServiceManager.getService(Context.OVERLAY_SERVICE)); + pm = context.getPackageManager(); + } + + public void setOverlayEnabled(String category, String packageName) { + final String currentPackageName = getOverlayInfos(category).stream() + .filter(info -> info.isEnabled()) + .map(info -> info.packageName) + .findFirst() + .orElse(null); + + try { + if ("android".equals(packageName)) { + mOverlayManager.setEnabled(currentPackageName, false, USER_SYSTEM); + } else { + mOverlayManager.setEnabledExclusiveInCategory(packageName, + USER_SYSTEM); + } + + writeSettings(category, packageName, "android".equals(packageName)); + + } catch (RemoteException e) { + } + } + + public void writeSettings(String category, String packageName, boolean disable) { + final String overlayPackageJson = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, USER_SYSTEM); + JSONObject object; + try { + if (overlayPackageJson == null) { + object = new JSONObject(); + } else { + object = new JSONObject(overlayPackageJson); + } + if (disable) { + if (object.has(category)) object.remove(category); + } else { + object.put(category, packageName); + } + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, + object.toString(), USER_SYSTEM); + } catch (JSONException e) { + Log.e(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e); + } + } + + public List getOverlayPackagesForCategory(String category) { + return getOverlayPackagesForCategory(category, "android"); + } + + public List getOverlayPackagesForCategory(String category, String target) { + List overlays = new ArrayList<>(); + overlays.add("android"); + for (OverlayInfo info : getOverlayInfos(category, target)) { + if (category.equals(info.getCategory())) { + overlays.add(info.getPackageName()); + } + } + return overlays; + } + + public List getOverlayInfos(String category) { + return getOverlayInfos(category, "android"); + } + + public List getOverlayInfos(String category, String target) { + final List filteredInfos = new ArrayList<>(); + try { + List overlayInfos = mOverlayManager + .getOverlayInfosForTarget(target, USER_SYSTEM); + for (OverlayInfo overlayInfo : overlayInfos) { + if (category.equals(overlayInfo.category)) { + filteredInfos.add(overlayInfo); + } + } + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + filteredInfos.sort(OVERLAY_INFO_COMPARATOR); + return filteredInfos; + } + + public List getLabels(String category) { + List labels = new ArrayList<>(); + labels.add("Default"); + for (OverlayInfo info : getOverlayInfos(category)) { + if (category.equals(info.getCategory())) { + try { + labels.add(pm.getApplicationInfo(info.packageName, 0) + .loadLabel(pm).toString()); + } catch (PackageManager.NameNotFoundException e) { + labels.add(info.packageName); + } + } + } + return labels; + } + + public List getFonts() { + final List fontlist = new ArrayList<>(); + for (String overlayPackage : getOverlayPackagesForCategory(FONT_KEY)) { + try { + overlayRes = overlayPackage.equals("android") ? Resources.getSystem() + : pm.getResourcesForApplication(overlayPackage); + final String font = overlayRes.getString( + overlayRes.getIdentifier("config_bodyFontFamily", + "string", overlayPackage)); + fontlist.add(Typeface.create(font, Typeface.NORMAL)); + } catch (NameNotFoundException | NotFoundException e) { + // Do nothing + } + } + return fontlist; + } + + public List getShapeDrawables() { + final List shapelist = new ArrayList<>(); + for (String overlayPackage : getOverlayPackagesForCategory(ICON_SHAPE_KEY)) { + shapelist.add(createShapeDrawable(overlayPackage)); + } + return shapelist; + } + + public ShapeDrawable createShapeDrawable(String overlayPackage) { + try { + if (overlayPackage.equals("android")) { + overlayRes = Resources.getSystem(); + } else { + if (overlayPackage.equals("default")) overlayPackage = "android"; + overlayRes = pm.getResourcesForApplication(overlayPackage); + } + } catch (NameNotFoundException | NotFoundException e) { + // Do nothing + } + final String shape = overlayRes.getString( + overlayRes.getIdentifier("config_icon_mask", + "string", overlayPackage)); + Path path = TextUtils.isEmpty(shape) ? null : PathParser.createPathFromPathData(shape); + PathShape pathShape = new PathShape(path, 100f, 100f); + ShapeDrawable shapeDrawable = new ShapeDrawable(pathShape); + int mThumbSize = (int) (mContext.getResources().getDisplayMetrics().density * 72); + shapeDrawable.setIntrinsicHeight(mThumbSize); + shapeDrawable.setIntrinsicWidth(mThumbSize); + return shapeDrawable; + } + + public boolean isOverlayEnabled(String overlayPackage) { + try { + OverlayInfo info = mOverlayManager.getOverlayInfo(overlayPackage, USER_SYSTEM); + return info == null ? false : info.isEnabled(); + } catch (RemoteException e) { + e.printStackTrace(); + } + return false; + } + + public boolean isDefaultOverlay(String category) { + for (String overlayPackage : getOverlayPackagesForCategory(category)) { + try { + OverlayInfo info = mOverlayManager.getOverlayInfo(overlayPackage, USER_SYSTEM); + if (info != null && info.isEnabled()) { + return false; + } else { + continue; + } + } catch (RemoteException e) { + e.printStackTrace(); + } + } + return true; + } +} \ No newline at end of file diff --git a/core/java/com/android/internal/util/custom/ThemesUtils.java b/core/java/com/android/internal/util/custom/ThemesUtils.java index 42d02b62f28d..71c6277a5a0f 100644 --- a/core/java/com/android/internal/util/custom/ThemesUtils.java +++ b/core/java/com/android/internal/util/custom/ThemesUtils.java @@ -80,4 +80,28 @@ public static void stockNewTileStyle(IOverlayManager om, int userId) { } } } + + // Statusbar Signal icons + private static final String[] SIGNAL_BAR = { + "org.blissroms.systemui.signalbar_a", + "org.blissroms.systemui.signalbar_b", + "org.blissroms.systemui.signalbar_c", + "org.blissroms.systemui.signalbar_d", + "org.blissroms.systemui.signalbar_e", + "org.blissroms.systemui.signalbar_f", + "org.blissroms.systemui.signalbar_g", + "org.blissroms.systemui.signalbar_h", + }; + + // Statusbar Wifi icons + private static final String[] WIFI_BAR = { + "org.blissroms.systemui.wifibar_a", + "org.blissroms.systemui.wifibar_b", + "org.blissroms.systemui.wifibar_c", + "org.blissroms.systemui.wifibar_d", + "org.blissroms.systemui.wifibar_e", + "org.blissroms.systemui.wifibar_f", + "org.blissroms.systemui.wifibar_g", + "org.blissroms.systemui.wifibar_h", + }; } diff --git a/core/res/res/layout/pocket_lock_view_layout.xml b/core/res/res/layout/pocket_lock_view_layout.xml new file mode 100644 index 000000000000..2eca671011d4 --- /dev/null +++ b/core/res/res/layout/pocket_lock_view_layout.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + diff --git a/core/res/res/values/custom_config.xml b/core/res/res/values/custom_config.xml index 3cc0d2a72e30..822b586a072a 100644 --- a/core/res/res/values/custom_config.xml +++ b/core/res/res/values/custom_config.xml @@ -213,5 +213,20 @@ - + + + + + + + + + + true + true + + + true + diff --git a/core/res/res/values/custom_strings.xml b/core/res/res/values/custom_strings.xml index c79757d71c60..57c6f7b2f775 100644 --- a/core/res/res/values/custom_strings.xml +++ b/core/res/res/values/custom_strings.xml @@ -49,4 +49,7 @@ SystemUI restart required For all changes to take effect, a SystemUI restart is required. Restart SystemUI now? + + + Press and hold power button to unlock diff --git a/core/res/res/values/custom_symbols.xml b/core/res/res/values/custom_symbols.xml index 57a35deff47f..6bf638f0104c 100644 --- a/core/res/res/values/custom_symbols.xml +++ b/core/res/res/values/custom_symbols.xml @@ -149,5 +149,20 @@ - + + + + + + + + + + + + + + + + diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 92757dcdb46d..f7bcfa84deb3 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -258,5 +258,6 @@ public boolean validate(String value) { }); VALIDATORS.put(System.FOD_GESTURE, BOOLEAN_VALIDATOR); VALIDATORS.put(System.ACCENT_COLOR, ANY_INTEGER_VALIDATOR); + VALIDATORS.put(System.POCKET_JUDGE, BOOLEAN_VALIDATOR); } } diff --git a/packages/SystemUI/res/drawable/sb_date_bg.xml b/packages/SystemUI/res/drawable/sb_date_bg.xml new file mode 100644 index 000000000000..b5e0acd088cc --- /dev/null +++ b/packages/SystemUI/res/drawable/sb_date_bg.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/packages/SystemUI/res/drawable/stat_sys_network_traffic_updown.xml b/packages/SystemUI/res/drawable/stat_sys_network_traffic_updown.xml new file mode 100644 index 000000000000..4e0b63ceffe4 --- /dev/null +++ b/packages/SystemUI/res/drawable/stat_sys_network_traffic_updown.xml @@ -0,0 +1,29 @@ + + + + + + diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml index 24dd7af329f1..6b1bf7917ee7 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml @@ -48,12 +48,23 @@ systemui:showDark="false" /> - + android:layout_weight="1" + android:gravity="center_vertical|center_horizontal" /> + + diff --git a/packages/SystemUI/res/values/custom_config.xml b/packages/SystemUI/res/values/custom_config.xml index 3163b2ac854f..fc309c00582f 100644 --- a/packages/SystemUI/res/values/custom_config.xml +++ b/packages/SystemUI/res/values/custom_config.xml @@ -35,4 +35,7 @@ This allows for the first block in PathEffect to fade --> 2 + + false + diff --git a/packages/SystemUI/res/values/custom_dimens.xml b/packages/SystemUI/res/values/custom_dimens.xml index 1ced556bc64c..6c878eb3a552 100644 --- a/packages/SystemUI/res/values/custom_dimens.xml +++ b/packages/SystemUI/res/values/custom_dimens.xml @@ -106,4 +106,5 @@ 13.5dp 20dp + diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 33061f0bbda1..14029eafc525 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -69,6 +69,8 @@ import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; +import android.pocket.IPocketCallback; +import android.pocket.PocketManager; import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; @@ -183,6 +185,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int MSG_USER_REMOVED = 341; private static final int MSG_KEYGUARD_GOING_AWAY = 342; + // Additional messages should be 600+ + private static final int MSG_POCKET_STATE_CHANGED = 600; + /** Biometric authentication state: Not listening. */ private static final int BIOMETRIC_STATE_STOPPED = 0; @@ -328,6 +333,27 @@ public void onChanged(Integer ringer) { } }; + private PocketManager mPocketManager; + private boolean mIsDeviceInPocket; + private final IPocketCallback mPocketCallback = new IPocketCallback.Stub() { + @Override + public void onStateChanged(boolean isDeviceInPocket, int reason) { + boolean wasInPocket = mIsDeviceInPocket; + if (reason == PocketManager.REASON_SENSOR) { + mIsDeviceInPocket = isDeviceInPocket; + } else { + mIsDeviceInPocket = false; + } + if (wasInPocket != mIsDeviceInPocket) { + mHandler.sendEmptyMessage(MSG_POCKET_STATE_CHANGED); + } + } + }; + + public boolean isPocketLockVisible(){ + return mPocketManager.isPocketLockVisible(); + } + private SparseBooleanArray mFaceSettingEnabledForUser = new SparseBooleanArray(); private BiometricManager mBiometricManager; private IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback = @@ -1707,6 +1733,9 @@ public void handleMessage(Message msg) { case MSG_KEYGUARD_GOING_AWAY: handleKeyguardGoingAway((boolean) msg.obj); break; + case MSG_POCKET_STATE_CHANGED: + updateFingerprintListeningState(); + break; default: super.handleMessage(msg); break; @@ -1788,6 +1817,11 @@ public void handleMessage(Message msg) { mDreamManager = IDreamManager.Stub.asInterface( ServiceManager.getService(DreamService.DREAM_SERVICE)); + mPocketManager = (PocketManager) context.getSystemService(Context.POCKET_SERVICE); + if (mPocketManager != null) { + mPocketManager.addCallback(mPocketCallback); + } + if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { mFpm = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE); } @@ -1973,14 +2007,14 @@ private boolean shouldListenForFingerprint() { (mKeyguardOccluded && mIsDreaming)) && mDeviceInteractive && !mGoingToSleep && !mSwitchingUser && !isFingerprintDisabled(getCurrentUser()) && (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser - && allowedOnBouncer; + && allowedOnBouncer && !mIsDeviceInPocket; } else { return (mKeyguardIsVisible || !mDeviceInteractive || (mBouncer && !mKeyguardGoingAway) || mGoingToSleep || shouldListenForFingerprintAssistant() || (mKeyguardOccluded && mIsDreaming)) && !mSwitchingUser && !isFingerprintDisabled(getCurrentUser()) && (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser - && allowedOnBouncer; + && allowedOnBouncer && !mIsDeviceInPocket; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java index 997c7ed11242..a046c16c0c9c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java @@ -81,6 +81,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private LinearLayout mCenterClockLayout; private View mRightClock; private boolean mShowClock = true; + private boolean mShowSBClockBg = true; private final Handler mHandler = new Handler(); private BatteryMeterView mBatteryMeterView; private StatusIconContainer mStatusIcons; @@ -98,6 +99,9 @@ void observe() { mContentResolver.registerContentObserver(Settings.System.getUriFor( Settings.System.STATUSBAR_CLOCK_STYLE), false, this, UserHandle.USER_ALL); + mContentResolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.STATUSBAR_CLOCK_CHIP), + false, this, UserHandle.USER_ALL); } @Override @@ -462,6 +466,22 @@ public void updateSettings(boolean animate) { Settings.System.STATUSBAR_CLOCK_STYLE, 0, UserHandle.USER_CURRENT); } + + mShowSBClockBg = Settings.System.getIntForUser(mContentResolver, + Settings.System.STATUSBAR_CLOCK_CHIP, 1, + UserHandle.USER_CURRENT) == 1; + + if (mShowSBClockBg) { + mClockView.setBackgroundResource(R.drawable.sb_date_bg); + mClockView.setPadding(10,5,10,5); + mRightClock.setBackgroundResource(R.drawable.sb_date_bg); + mRightClock.setPadding(10,5,10,5); + } else { + mClockView.setBackgroundResource(0); + mClockView.setPadding(0,0,0,0); + mRightClock.setBackgroundResource(0); + mRightClock.setPadding(0,0,0,0); + } updateClockStyle(animate); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java index 8f20034a17c3..aae19a2e7219 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java @@ -22,6 +22,7 @@ import android.content.res.Resources; import android.view.View; +import com.android.internal.util.custom.CustomUtils; import com.android.systemui.R; public final class PhoneStatusBarTransitions extends BarTransitions { @@ -104,7 +105,6 @@ private void applyMode(int mode, boolean animate) { mLeftSide.setAlpha(newAlpha); mStatusIcons.setAlpha(newAlpha); mBattery.setAlpha(newAlphaBC); - mClock.setAlpha(newAlphaBC); mCenterClock.setAlpha(newAlphaBC); mRightClock.setAlpha(newAlphaBC); } 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 c3c442b98f62..2a3e2a0dd41d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1994,6 +1994,7 @@ public void setPanelExpanded(boolean isExpanded) { if (!isExpanded) { mRemoteInputManager.onPanelCollapsed(); } + ((StatusBarIconControllerImpl) mIconController).onPanelExpanded(isExpanded); } public ViewGroup getNotificationScrollLayout() { @@ -4125,6 +4126,8 @@ public void onStateChanged(int newState) { mPresenter.updateMediaMetaData(false, mState != StatusBarState.KEYGUARD); mPulseController.setKeyguardShowing(mState == StatusBarState.KEYGUARD); updateKeyguardState(); + + ((StatusBarIconControllerImpl) mIconController).setKeyguardShowing(mState == StatusBarState.KEYGUARD); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index d74bc9b96a66..e1d84f03a8a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -19,6 +19,7 @@ import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON; import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE; +import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_NETWORK_TRAFFIC; import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI; import android.content.Context; @@ -47,6 +48,7 @@ import com.android.systemui.statusbar.StatusIconDisplayable; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; +import com.android.systemui.statusbar.policy.NetworkTrafficSB; import com.android.systemui.util.Utils.DisableStateTracker; import java.util.List; @@ -260,6 +262,9 @@ protected StatusIconDisplayable addHolder(int index, String slot, boolean blocke case TYPE_MOBILE: return addMobileIcon(index, slot, holder.getMobileState()); + + case TYPE_NETWORK_TRAFFIC: + return addNetworkTraffic(index, slot); } return null; @@ -286,6 +291,12 @@ protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState return view; } + protected NetworkTrafficSB addNetworkTraffic(int index, String slot) { + NetworkTrafficSB view = onCreateNetworkTraffic(slot); + mGroup.addView(view, index, onCreateLayoutParams()); + return view; + } + @VisibleForTesting protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) { StatusBarMobileView view = onCreateStatusBarMobileView(slot); @@ -312,6 +323,12 @@ private StatusBarMobileView onCreateStatusBarMobileView(String slot) { return view; } + private NetworkTrafficSB onCreateNetworkTraffic(String slot) { + NetworkTrafficSB view = new NetworkTrafficSB(mContext); + view.setPadding(2, 0, 2, 0); + return view; + } + protected LinearLayout.LayoutParams onCreateLayoutParams() { return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize); } @@ -426,5 +443,21 @@ protected void exitDemoMode() { protected DemoStatusIcons createDemoStatusIcons() { return new DemoStatusIcons((LinearLayout) mGroup, mIconSize); } + + public void onPanelExpanded(boolean isExpanded) { + for (int i = 0; i < mGroup.getChildCount(); i++) { + if (mGroup.getChildAt(i) instanceof NetworkTrafficSB) { + ((NetworkTrafficSB)mGroup.getChildAt(i)).onPanelExpanded(isExpanded); + } + } + } + + public void setKeyguardShowing(boolean showing) { + for (int i = 0; i < mGroup.getChildCount(); i++) { + if (mGroup.getChildAt(i) instanceof NetworkTrafficSB) { + ((NetworkTrafficSB)mGroup.getChildAt(i)).setKeyguardShowing(showing); + } + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java index d0e806769f14..89c9bd562222 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java @@ -347,6 +347,18 @@ public void dispatchDemoCommand(String command, Bundle args) { } } + public void onPanelExpanded(boolean isExpanded) { + for (IconManager manager : mIconGroups) { + manager.onPanelExpanded(isExpanded); + } + } + + public void setKeyguardShowing(boolean showing) { + for (IconManager manager : mIconGroups) { + manager.setKeyguardShowing(showing); + } + } + @Override public void onDensityOrFontScaleChanged() { loadDimens(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java index 88d0035b333d..3ca701db6cb7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java @@ -32,6 +32,7 @@ public class StatusBarIconHolder { public static final int TYPE_ICON = 0; public static final int TYPE_WIFI = 1; public static final int TYPE_MOBILE = 2; + public static final int TYPE_NETWORK_TRAFFIC = 3; private StatusBarIcon mIcon; private WifiIconState mWifiState; @@ -70,6 +71,12 @@ public static StatusBarIconHolder fromMobileIconState(MobileIconState state) { return holder; } + public static StatusBarIconHolder fromNetworkTraffic() { + StatusBarIconHolder holder = new StatusBarIconHolder(); + holder.mType = TYPE_NETWORK_TRAFFIC; + return holder; + } + public int getType() { return mType; } @@ -105,6 +112,8 @@ public boolean isVisible() { return mWifiState.visible; case TYPE_MOBILE: return mMobileState.visible; + case TYPE_NETWORK_TRAFFIC: + return true; default: return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java index c876c3228eb8..4d5e3b7befb2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java @@ -27,6 +27,8 @@ import java.util.ArrayList; import java.util.List; +import com.android.systemui.statusbar.policy.NetworkTrafficSB; + public class StatusBarIconList { private ArrayList mSlots = new ArrayList<>(); @@ -35,6 +37,9 @@ public StatusBarIconList(String[] slots) { for (int i=0; i < N; i++) { mSlots.add(new Slot(slots[i], null)); } + + // Network traffic slot + mSlots.add(0, new Slot(NetworkTrafficSB.SLOT, StatusBarIconHolder.fromNetworkTraffic())); } public int getSlotIndex(String slot) { @@ -45,8 +50,8 @@ public int getSlotIndex(String slot) { return i; } } - // Auto insert new items at the beginning. - mSlots.add(0, new Slot(slot, null)); + // Auto insert new items behind network traffic + mSlots.add(1, new Slot(slot, null)); return 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index fcb97ef643e3..c23a0e44d1ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -78,6 +78,7 @@ public class MobileSignalController extends SignalController< private final String mNetworkNameDefault; private final String mNetworkNameSeparator; private final ContentObserver mObserver; + private final Handler mHandler = new Handler(); @VisibleForTesting final PhoneStateListener mPhoneStateListener; // Save entire info for logging, we only use the id. @@ -600,6 +601,7 @@ private void removeListeners() { @Override public void notifyListeners(SignalCallback callback) { + mHandler.post(() -> { MobileIconGroup icons = getIcons(); String contentDescription = getTextIfExists(getContentDescription()).toString(); @@ -654,6 +656,7 @@ public void notifyListeners(SignalCallback callback) { activityIn, activityOut, volteIcon, dataContentDescription, dataContentDescriptionHtml, description, icons.mIsWide, mSubscriptionInfo.getSubscriptionId(), mCurrentState.roaming); + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkTraffic.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkTraffic.java new file mode 100644 index 000000000000..83399cc4c381 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkTraffic.java @@ -0,0 +1,358 @@ +package com.android.systemui.statusbar.policy; + +import java.text.DecimalFormat; + +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.graphics.drawable.Drawable; +import android.graphics.PorterDuff.Mode; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.view.Gravity; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.TrafficStats; +import android.os.Handler; +import android.os.UserHandle; +import android.os.Message; +import android.os.SystemClock; +import android.provider.Settings; +import android.text.Spanned; +import android.text.SpannableString; +import android.text.style.RelativeSizeSpan; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; +import android.widget.TextView; + +import com.android.systemui.R; + +/* +* +* Seeing how an Integer object in java requires at least 16 Bytes, it seemed awfully wasteful +* to only use it for a single boolean. 32-bits is plenty of room for what we need it to do. +* +*/ +public class NetworkTraffic extends TextView { + + private static final int INTERVAL = 1500; //ms + private static final int KB = 1024; + private static final int MB = KB * KB; + private static final int GB = MB * KB; + private static final String symbol = "/S"; + + protected boolean mIsEnabled; + private boolean mAttached; + private long totalRxBytes; + private long totalTxBytes; + private long lastUpdateTime; + private int mAutoHideThreshold; + protected int mTintColor; + + private boolean mScreenOn = true; + protected boolean mVisible = true; + private ConnectivityManager mConnectivityManager; + + private Handler mTrafficHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + long timeDelta = SystemClock.elapsedRealtime() - lastUpdateTime; + + if (timeDelta < INTERVAL * .95) { + if (msg.what != 1) { + // we just updated the view, nothing further to do + return; + } + if (timeDelta < 1) { + // Can't div by 0 so make sure the value displayed is minimal + timeDelta = Long.MAX_VALUE; + } + } + lastUpdateTime = SystemClock.elapsedRealtime(); + + // Calculate the data rate from the change in total bytes and time + long newTotalRxBytes = TrafficStats.getTotalRxBytes(); + long newTotalTxBytes = TrafficStats.getTotalTxBytes(); + long rxData = newTotalRxBytes - totalRxBytes; + long txData = newTotalTxBytes - totalTxBytes; + + if (shouldHide(rxData, txData, timeDelta)) { + setText(""); + setVisibility(View.GONE); + mVisible = false; + } else if (shouldShowUpload(rxData, txData, timeDelta)) { + // Show information for uplink if it's called for + CharSequence output = formatOutput(timeDelta, txData, symbol); + + // Update view if there's anything new to show + if (output != getText()) { + setText(output); + } + } else { + // Add information for downlink if it's called for + CharSequence output = formatOutput(timeDelta, rxData, symbol); + + // Update view if there's anything new to show + if (output != getText()) { + setText(output); + } + makeVisible(); + } + + // Post delayed message to refresh in ~1000ms + totalRxBytes = newTotalRxBytes; + totalTxBytes = newTotalTxBytes; + clearHandlerCallbacks(); + mTrafficHandler.postDelayed(mRunnable, INTERVAL); + } + + private CharSequence formatOutput(long timeDelta, long data, String symbol) { + long speed = (long)(data / (timeDelta / 1000F)); + + return formatDecimal(speed); + } + + private CharSequence formatDecimal(long speed) { + DecimalFormat decimalFormat; + String unit; + String formatSpeed; + SpannableString spanUnitString; + SpannableString spanSpeedString; + + if (speed >= GB) { + unit = "GB"; + decimalFormat = new DecimalFormat("0.00"); + formatSpeed = decimalFormat.format(speed / (float)GB); + } else if (speed >= 100 * MB) { + decimalFormat = new DecimalFormat("000"); + unit = "MB"; + formatSpeed = decimalFormat.format(speed / (float)MB); + } else if (speed >= 10 * MB) { + decimalFormat = new DecimalFormat("00.0"); + unit = "MB"; + formatSpeed = decimalFormat.format(speed / (float)MB); + } else if (speed >= MB) { + decimalFormat = new DecimalFormat("0.00"); + unit = "MB"; + formatSpeed = decimalFormat.format(speed / (float)MB); + } else if (speed >= 100 * KB) { + decimalFormat = new DecimalFormat("000"); + unit = "KB"; + formatSpeed = decimalFormat.format(speed / (float)KB); + } else if (speed >= 10 * KB) { + decimalFormat = new DecimalFormat("00.0"); + unit = "KB"; + formatSpeed = decimalFormat.format(speed / (float)KB); + } else { + decimalFormat = new DecimalFormat("0.00"); + unit = "KB"; + formatSpeed = decimalFormat.format(speed / (float)KB); + } + spanSpeedString = new SpannableString(formatSpeed); + spanSpeedString.setSpan(getSpeedRelativeSizeSpan(), 0, (formatSpeed).length(), + Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + spanUnitString = new SpannableString(unit + symbol); + spanUnitString.setSpan(getUnitRelativeSizeSpan(), 0, (unit + symbol).length(), + Spanned.SPAN_INCLUSIVE_INCLUSIVE); + return TextUtils.concat(spanSpeedString, "\n", spanUnitString); + } + + private boolean shouldHide(long rxData, long txData, long timeDelta) { + long speedRxKB = (long)(rxData / (timeDelta / 1000f)) / KB; + long speedTxKB = (long)(txData / (timeDelta / 1000f)) / KB; + return !getConnectAvailable() || + (speedRxKB < mAutoHideThreshold && + speedTxKB < mAutoHideThreshold); + } + + private boolean shouldShowUpload(long rxData, long txData, long timeDelta) { + long speedRxKB = (long)(rxData / (timeDelta / 1000f)) / KB; + long speedTxKB = (long)(txData / (timeDelta / 1000f)) / KB; + + return (speedTxKB > speedRxKB); + } + }; + + protected boolean restoreViewQuickly() { + return getConnectAvailable() && mAutoHideThreshold == 0; + } + + protected void makeVisible() { + setVisibility(View.VISIBLE); + mVisible = true; + } + + /* + * @hide + */ + public NetworkTraffic(Context context) { + this(context, null); + } + + /* + * @hide + */ + public NetworkTraffic(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + /* + * @hide + */ + public NetworkTraffic(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + final Resources resources = getResources(); + mTintColor = resources.getColor(android.R.color.white); + setMode(); + Handler mHandler = new Handler(); + mConnectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + SettingsObserver settingsObserver = new SettingsObserver(mHandler); + settingsObserver.observe(); + update(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (!mAttached) { + mAttached = true; + IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_SCREEN_ON); + mContext.registerReceiver(mIntentReceiver, filter, null, getHandler()); + } + update(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mAttached) { + mContext.unregisterReceiver(mIntentReceiver); + mAttached = false; + } + } + + protected RelativeSizeSpan getSpeedRelativeSizeSpan() { + return new RelativeSizeSpan(0.78f); + } + + protected RelativeSizeSpan getUnitRelativeSizeSpan() { + return new RelativeSizeSpan(0.70f); + } + + private Runnable mRunnable = new Runnable() { + @Override + public void run() { + mTrafficHandler.sendEmptyMessage(0); + } + }; + + class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + void observe() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.System + .getUriFor(Settings.System.NETWORK_TRAFFIC_STATE), false, + this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System + .getUriFor(Settings.System.NETWORK_TRAFFIC_AUTOHIDE_THRESHOLD), false, + this, UserHandle.USER_ALL); + } + + /* + * @hide + */ + @Override + public void onChange(boolean selfChange) { + setMode(); + update(); + } + } + + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) return; + if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) && mScreenOn) { + update(); + } else if (action.equals(Intent.ACTION_SCREEN_ON)) { + mScreenOn = true; + update(); + } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { + mScreenOn = false; + clearHandlerCallbacks(); + } + } + }; + + private boolean getConnectAvailable() { + NetworkInfo network = (mConnectivityManager != null) ? mConnectivityManager.getActiveNetworkInfo() : null; + return network != null; + } + + protected void update() { + if (mIsEnabled) { + if (mAttached) { + totalRxBytes = TrafficStats.getTotalRxBytes(); + lastUpdateTime = SystemClock.elapsedRealtime(); + mTrafficHandler.sendEmptyMessage(1); + } + return; + } else { + clearHandlerCallbacks(); + } + setVisibility(View.GONE); + mVisible = false; + } + + protected void setMode() { + ContentResolver resolver = mContext.getContentResolver(); + mIsEnabled = Settings.System.getIntForUser(resolver, + Settings.System.NETWORK_TRAFFIC_STATE, 0, + UserHandle.USER_CURRENT) == 1; + mAutoHideThreshold = Settings.System.getIntForUser(resolver, + Settings.System.NETWORK_TRAFFIC_AUTOHIDE_THRESHOLD, 1, + UserHandle.USER_CURRENT); + setGravity(Gravity.CENTER); + setMaxLines(2); + setSpacingAndFonts(); + updateTrafficDrawable(); + setVisibility(View.GONE); + mVisible = false; + } + + private void clearHandlerCallbacks() { + mTrafficHandler.removeCallbacks(mRunnable); + mTrafficHandler.removeMessages(0); + mTrafficHandler.removeMessages(1); + } + + protected void updateTrafficDrawable() { + setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + setTextColor(mTintColor); + } + + protected void setSpacingAndFonts() { + setTypeface(Typeface.create(Typeface.DEFAULT, 600, false)); + setLineSpacing(0.85f, 0.85f); + } + + public void onDensityOrFontScaleChanged() { + setSpacingAndFonts(); + update(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkTrafficSB.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkTrafficSB.java new file mode 100644 index 000000000000..82c884e48d37 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkTrafficSB.java @@ -0,0 +1,188 @@ +package com.android.systemui.statusbar.policy; + +import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT; +import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN; +import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON; + +import android.content.Context; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.text.style.RelativeSizeSpan; +import android.util.AttributeSet; +import android.view.View; + +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.plugins.DarkIconDispatcher; +import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.statusbar.StatusIconDisplayable; + +public class NetworkTrafficSB extends NetworkTraffic implements DarkReceiver, StatusIconDisplayable { + + public static final String SLOT = "networktraffic"; + private int mVisibleState = -1; + private boolean mTrafficVisible = false; + private boolean mSystemIconVisible = true; + private boolean mStatusbarExpanded; + private boolean mKeyguardShowing; + + /* + * @hide + */ + public NetworkTrafficSB(Context context) { + this(context, null); + } + + /* + * @hide + */ + public NetworkTrafficSB(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + /* + * @hide + */ + public NetworkTrafficSB(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + Dependency.get(DarkIconDispatcher.class).addDarkReceiver(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(this); + } + + @Override + protected void setMode() { + super.setMode(); + mIsEnabled = mIsEnabled && shouldShowOnSB(mContext); + } + + @Override + protected void setSpacingAndFonts() { + setTypeface(Typeface.create(Typeface.DEFAULT, 600, false)); + setLineSpacing(0.80f, 0.80f); + } + + @Override + protected RelativeSizeSpan getSpeedRelativeSizeSpan() { + return new RelativeSizeSpan(0.70f); + } + + @Override + protected RelativeSizeSpan getUnitRelativeSizeSpan() { + return new RelativeSizeSpan(0.60f); + } + + @Override + public void onDarkChanged(Rect area, float darkIntensity, int tint) { + if (!mIsEnabled) return; + mTintColor = DarkIconDispatcher.getTint(area, this, tint); + setTextColor(mTintColor); + updateTrafficDrawable(); + } + + @Override + public String getSlot() { + return SLOT; + } + + @Override + public boolean isIconVisible() { + return mIsEnabled; + } + + @Override + public int getVisibleState() { + return mVisibleState; + } + + @Override + public void setVisibleState(int state, boolean mIsEnabled) { + if (state == mVisibleState) { + return; + } + mVisibleState = state; + + switch (state) { + case STATE_ICON: + mSystemIconVisible = true; + break; + case STATE_DOT: + case STATE_HIDDEN: + default: + mSystemIconVisible = false; + break; + } + update(); + } + + @Override + protected void makeVisible() { + boolean show = !mStatusbarExpanded && mSystemIconVisible && !mKeyguardShowing; + setVisibility(show ? View.VISIBLE + : View.GONE); + mVisible = show; + } + + @Override + public void setStaticDrawableColor(int color) { + mTintColor = color; + setTextColor(mTintColor); + updateTrafficDrawable(); + } + + @Override + public void setDecorColor(int color) { + } + + public void onPanelExpanded(boolean isExpanded) { + mStatusbarExpanded = isExpanded; + if (isExpanded) { + setVisibility(View.GONE); + mVisible = false; + } else { + maybeRestoreVisibility(); + } + } + + public void setKeyguardShowing(boolean showing) { + mKeyguardShowing = showing; + if (showing) { + setVisibility(View.GONE); + mVisible = false; + } else { + maybeRestoreVisibility(); + } + } + + private void maybeRestoreVisibility() { + if (!mVisible && mIsEnabled && !mStatusbarExpanded && !mKeyguardShowing && mSystemIconVisible + && restoreViewQuickly()) { + setVisibility(View.VISIBLE); + mVisible = true; + // then let the traffic handler do its checks + update(); + } + } + + private static boolean shouldShowOnSB(Context context) { + if (context.getResources().getBoolean(R.bool.config_forceShowNetworkTrafficOnStatusBar)) + return true; + + int cutoutResId = context.getResources().getIdentifier("config_mainBuiltInDisplayCutout", + "string", "android"); + if (cutoutResId > 0) { + return context.getResources().getString(cutoutResId).equals(""); + } + + return true; + } +} diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 0ddfa1c16a0a..a7f9675477ad 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -1600,6 +1600,17 @@ public void setFirewallInterfaceRule(String iface, boolean allow) { } } + @Override + public void setFirewallMACAddressRule(String macAddr, boolean allow) { + enforceSystemUid(); + try { + mNetdService.firewallSetMACAddressRule(macAddr, + allow ? INetd.FIREWALL_RULE_ALLOW : INetd.FIREWALL_RULE_DENY); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); + } + } + private void closeSocketsForFirewallChainLocked(int chain, String chainName) { // UID ranges to close sockets on. UidRangeParcel[] ranges; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index 77e2fbd25670..1c255e3fca03 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -679,7 +679,7 @@ private static void setDisabledUntilUsed(IPackageManager packageManager, String final int state; try { state = packageManager.getApplicationEnabledSetting(packageName, userId); - } catch (RemoteException e) { + } catch (Exception e) { Slog.w(TAG, "getApplicationEnabledSetting failed. packageName=" + packageName + " userId=" + userId, e); return; @@ -693,7 +693,7 @@ private static void setDisabledUntilUsed(IPackageManager packageManager, String packageManager.setApplicationEnabledSetting(packageName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0 /* newState */, userId, callingPackage); - } catch (RemoteException e) { + } catch (Exception e) { Slog.w(TAG, "setApplicationEnabledSetting failed. packageName=" + packageName + " userId=" + userId + " callingPackage=" + callingPackage, e); return; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 56d343913820..f507fd50ff40 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4007,7 +4007,7 @@ private void updateInstantAppInstallerLocked(String modifiedPackage) { // NOTE: When no BUILD_NUMBER is set by the build system, it defaults to a build // that starts with "eng." to signify that this is an engineering build and not // destined for release. - if (Build.IS_USERDEBUG && Build.VERSION.INCREMENTAL.startsWith("eng.")) { + if (isUpgrade || Build.VERSION.INCREMENTAL.startsWith("eng.")) { Slog.w(TAG, "Wiping cache directory because the system partition changed."); // Heuristic: If the /system directory has been modified recently due to an "adb sync" @@ -12458,7 +12458,8 @@ private void commitPackageSettings(AndroidPackage pkg, mResolveActivity.processName = "system:ui"; mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER; - mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; + mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS + | ActivityInfo.FLAG_HARDWARE_ACCELERATED; mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert; mResolveActivity.exported = true; mResolveActivity.enabled = true; @@ -15109,7 +15110,9 @@ private int installLocationPolicy(PackageInfoLite pkgLite) { if (dataOwnerPkg != null) { if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags, - dataOwnerPkg.isDebuggable())) { + dataOwnerPkg.isDebuggable()) + && Global.getInt(mContext.getContentResolver(), + Global.PM_DOWNGRADE_ALLOWED, 0) == 0) { try { checkDowngrade(dataOwnerPkg, pkgLite); } catch (PackageManagerException e) { diff --git a/services/core/java/com/android/server/pocket/PocketBridgeService.java b/services/core/java/com/android/server/pocket/PocketBridgeService.java new file mode 100644 index 000000000000..5fc5e2721cc4 --- /dev/null +++ b/services/core/java/com/android/server/pocket/PocketBridgeService.java @@ -0,0 +1,184 @@ +/** + * Copyright (C) 2017 The ParanoidAndroid Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.pocket; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; + +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.UserHandle; +import android.pocket.IPocketCallback; +import android.pocket.PocketManager; +import android.provider.Settings.System; +import android.util.Slog; +import com.android.internal.util.FastPrintWriter; +import com.android.server.SystemService; + +import static android.provider.Settings.System.POCKET_JUDGE; + +/** + * This service communicates pocket state to the pocket judge kernel driver. + * It maintains the pocket state by binding to the pocket service. + * + * @author Chris Lahaye + * @hide + */ +public class PocketBridgeService extends SystemService { + + private static final String TAG = PocketBridgeService.class.getSimpleName(); + private static final int MSG_POCKET_STATE_CHANGED = 1; + + private Context mContext; + private boolean mEnabled; + private PocketBridgeHandler mHandler; + private PocketBridgeObserver mObserver; + + private PocketManager mPocketManager; + private boolean mIsDeviceInPocket; + private final IPocketCallback mPocketCallback = new IPocketCallback.Stub() { + @Override + public void onStateChanged(boolean isDeviceInPocket, int reason) { + boolean changed = false; + if (reason == PocketManager.REASON_SENSOR) { + if (isDeviceInPocket != mIsDeviceInPocket) { + mIsDeviceInPocket = isDeviceInPocket; + changed = true; + } + } else { + changed = isDeviceInPocket != mIsDeviceInPocket; + mIsDeviceInPocket = false; + } + if (changed) { + mHandler.sendEmptyMessage(MSG_POCKET_STATE_CHANGED); + } + } + }; + + // Custom methods + private boolean mSupportedByDevice; + + public PocketBridgeService(Context context) { + super(context); + mContext = context; + HandlerThread handlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); + handlerThread.start(); + mHandler = new PocketBridgeHandler(handlerThread.getLooper()); + mPocketManager = (PocketManager) + context.getSystemService(Context.POCKET_SERVICE); + mSupportedByDevice = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_pocketModeSupported); + mObserver = new PocketBridgeObserver(mHandler); + if (mSupportedByDevice){ + mObserver.onChange(true); + mObserver.register(); + } + } + + @Override + public void onStart() { + } + + private void setEnabled(boolean enabled) { + if (enabled != mEnabled) { + mEnabled = enabled; + update(); + } + } + + private void update() { + if (!mSupportedByDevice || mPocketManager == null) return; + + if (mEnabled) { + mPocketManager.addCallback(mPocketCallback); + } else { + mPocketManager.removeCallback(mPocketCallback); + } + } + + private class PocketBridgeHandler extends Handler { + + private FileOutputStream mFileOutputStream; + private FastPrintWriter mPrintWriter; + + public PocketBridgeHandler(Looper looper) { + super(looper); + + try { + mFileOutputStream = new FileOutputStream( + mContext.getResources().getString( + com.android.internal.R.string.config_pocketBridgeSysfsInpocket) + ); + mPrintWriter = new FastPrintWriter(mFileOutputStream, true, 128); + } + catch(FileNotFoundException e) { + Slog.w(TAG, "Pocket bridge error occured", e); + setEnabled(false); + } + } + + @Override + public void handleMessage(android.os.Message msg) { + if (msg.what != MSG_POCKET_STATE_CHANGED) { + Slog.w(TAG, "Unknown message:" + msg.what); + return; + } + + if (mPrintWriter != null) { + mPrintWriter.println(mIsDeviceInPocket ? 1 : 0); + } + } + + } + + private class PocketBridgeObserver extends ContentObserver { + + private boolean mRegistered; + + public PocketBridgeObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + final boolean enabled = System.getIntForUser(mContext.getContentResolver(), + POCKET_JUDGE, 0 /* default */, UserHandle.USER_CURRENT) != 0; + setEnabled(enabled); + } + + public void register() { + if (!mRegistered) { + mContext.getContentResolver().registerContentObserver( + System.getUriFor(POCKET_JUDGE), true, this); + mRegistered = true; + } + } + + public void unregister() { + if (mRegistered) { + mContext.getContentResolver().unregisterContentObserver(this); + mRegistered = false; + } + } + + } + +} diff --git a/services/core/java/com/android/server/pocket/PocketService.java b/services/core/java/com/android/server/pocket/PocketService.java new file mode 100644 index 000000000000..28a757e5a7b3 --- /dev/null +++ b/services/core/java/com/android/server/pocket/PocketService.java @@ -0,0 +1,914 @@ +/** + * Copyright (C) 2016 The ParanoidAndroid Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.pocket; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Binder; +import android.os.DeadObjectException; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.pocket.IPocketService; +import android.pocket.IPocketCallback; +import android.pocket.PocketConstants; +import android.pocket.PocketManager; +import android.provider.Settings.System; +import android.text.TextUtils; +import android.util.Log; +import android.util.Slog; + +import com.android.server.SystemService; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; + +import static android.provider.Settings.System.POCKET_JUDGE; + +/** + * A service to manage multiple clients that want to listen for pocket state. + * The service is responsible for maintaining a list of clients and dispatching all + * pocket -related information. + * + * @author Carlo Savignano + * @hide + */ +public class PocketService extends SystemService implements IBinder.DeathRecipient { + + private static final String TAG = PocketService.class.getSimpleName(); + private static final boolean DEBUG = PocketConstants.DEBUG; + + /** + * Wheater we don't have yet a valid vendor sensor event or pocket service not running. + */ + private static final int VENDOR_SENSOR_UNKNOWN = 0; + + /** + * Vendor sensor has been registered, onSensorChanged() has been called and we have a + * valid event value from Vendor pocket sensor. + */ + private static final int VENDOR_SENSOR_IN_POCKET = 1; + + /** + * The rate proximity sensor events are delivered at. + */ + private static final int PROXIMITY_SENSOR_DELAY = 400000; + + /** + * Wheater we don't have yet a valid proximity sensor event or pocket service not running. + */ + private static final int PROXIMITY_UNKNOWN = 0; + + /** + * Proximity sensor has been registered, onSensorChanged() has been called and we have a + * valid event value which determined proximity sensor is covered. + */ + private static final int PROXIMITY_POSITIVE = 1; + + /** + * Proximity sensor has been registered, onSensorChanged() has been called and we have a + * valid event value which determined proximity sensor is not covered. + */ + private static final int PROXIMITY_NEGATIVE = 2; + + /** + * The rate light sensor events are delivered at. + */ + private static final int LIGHT_SENSOR_DELAY = 400000; + + /** + * Wheater we don't have yet a valid light sensor event or pocket service not running. + */ + private static final int LIGHT_UNKNOWN = 0; + + /** + * Light sensor has been registered, onSensorChanged() has been called and we have a + * valid event value which determined available light is in pocket range. + */ + private static final int LIGHT_POCKET = 1; + + /** + * Light sensor has been registered, onSensorChanged() has been called and we have a + * valid event value which determined available light is outside pocket range. + */ + private static final int LIGHT_AMBIENT = 2; + + /** + * Light sensor maximum value registered in pocket with up to semi-transparent fabric. + */ + private static final float POCKET_LIGHT_MAX_THRESHOLD = 3.0f; + + private final ArrayList mCallbacks= new ArrayList<>(); + + private Context mContext; + private boolean mEnabled; + private boolean mSystemReady; + private boolean mSystemBooted; + private boolean mInteractive; + private boolean mPending; + private PocketHandler mHandler; + private PocketObserver mObserver; + private SensorManager mSensorManager; + + // proximity + private int mProximityState = PROXIMITY_UNKNOWN; + private int mLastProximityState = PROXIMITY_UNKNOWN; + private float mProximityMaxRange; + private boolean mProximityRegistered; + private Sensor mProximitySensor; + + // light + private int mLightState = LIGHT_UNKNOWN; + private int mLastLightState = LIGHT_UNKNOWN; + private float mLightMaxRange; + private boolean mLightRegistered; + private Sensor mLightSensor; + + // vendor sensor + private int mVendorSensorState = VENDOR_SENSOR_UNKNOWN; + private int mLastVendorSensorState = VENDOR_SENSOR_UNKNOWN; + private String mVendorPocketSensor; + private boolean mVendorSensorRegistered; + private Sensor mVendorSensor; + + // Custom methods + private boolean mPocketLockVisible; + private boolean mSupportedByDevice; + + public PocketService(Context context) { + super(context); + mContext = context; + HandlerThread handlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); + handlerThread.start(); + mHandler = new PocketHandler(handlerThread.getLooper()); + mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE); + mVendorPocketSensor = mContext.getResources().getString( + com.android.internal.R.string.config_pocketJudgeVendorSensorName); + String vendorProximitySensor = mContext.getResources().getString( + com.android.internal.R.string.config_pocketJudgeVendorProximitySensorName); + if (vendorProximitySensor != null && !vendorProximitySensor.isEmpty()) { + mProximitySensor = getSensor(mSensorManager, vendorProximitySensor); + } else { + mProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); + } + if (mProximitySensor != null) { + mProximityMaxRange = mProximitySensor.getMaximumRange(); + } + mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); + if (mLightSensor != null) { + mLightMaxRange = mLightSensor.getMaximumRange(); + } + mVendorSensor = getSensor(mSensorManager, mVendorPocketSensor); + mSupportedByDevice = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_pocketModeSupported); + mObserver = new PocketObserver(mHandler); + if (mSupportedByDevice){ + mObserver.onChange(true); + mObserver.register(); + } + } + + private class PocketObserver extends ContentObserver { + + private boolean mRegistered; + + public PocketObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + final boolean enabled = System.getIntForUser(mContext.getContentResolver(), + POCKET_JUDGE, 0 /* default */, UserHandle.USER_CURRENT) != 0; + setEnabled(enabled); + } + + public void register() { + if (!mRegistered) { + mContext.getContentResolver().registerContentObserver( + System.getUriFor(POCKET_JUDGE), true, this); + mRegistered = true; + } + } + + public void unregister() { + if (mRegistered) { + mContext.getContentResolver().unregisterContentObserver(this); + mRegistered = false; + } + } + + } + + private class PocketHandler extends Handler { + + public static final int MSG_SYSTEM_READY = 0; + public static final int MSG_SYSTEM_BOOTED = 1; + public static final int MSG_DISPATCH_CALLBACKS = 2; + public static final int MSG_ADD_CALLBACK = 3; + public static final int MSG_REMOVE_CALLBACK = 4; + public static final int MSG_INTERACTIVE_CHANGED = 5; + public static final int MSG_SENSOR_EVENT_PROXIMITY = 6; + public static final int MSG_SENSOR_EVENT_LIGHT = 7; + public static final int MSG_UNREGISTER_TIMEOUT = 8; + public static final int MSG_SET_LISTEN_EXTERNAL = 9; + public static final int MSG_SET_POCKET_LOCK_VISIBLE = 10; + public static final int MSG_SENSOR_EVENT_VENDOR = 11; + + public PocketHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(android.os.Message msg) { + switch (msg.what) { + case MSG_SYSTEM_READY: + handleSystemReady(); + break; + case MSG_SYSTEM_BOOTED: + handleSystemBooted(); + break; + case MSG_DISPATCH_CALLBACKS: + handleDispatchCallbacks(); + break; + case MSG_ADD_CALLBACK: + handleAddCallback((IPocketCallback) msg.obj); + break; + case MSG_REMOVE_CALLBACK: + handleRemoveCallback((IPocketCallback) msg.obj); + break; + case MSG_INTERACTIVE_CHANGED: + handleInteractiveChanged(msg.arg1 != 0); + break; + case MSG_SENSOR_EVENT_PROXIMITY: + handleProximitySensorEvent((SensorEvent) msg.obj); + break; + case MSG_SENSOR_EVENT_LIGHT: + handleLightSensorEvent((SensorEvent) msg.obj); + break; + case MSG_SENSOR_EVENT_VENDOR: + handleVendorSensorEvent((SensorEvent) msg.obj); + break; + case MSG_UNREGISTER_TIMEOUT: + handleUnregisterTimeout(); + break; + case MSG_SET_LISTEN_EXTERNAL: + handleSetListeningExternal(msg.arg1 != 0); + break; + case MSG_SET_POCKET_LOCK_VISIBLE: + handleSetPocketLockVisible(msg.arg1 != 0); + break; + default: + Slog.w(TAG, "Unknown message:" + msg.what); + } + } + } + + @Override + public void onBootPhase(int phase) { + switch(phase) { + case PHASE_SYSTEM_SERVICES_READY: + mHandler.sendEmptyMessage(PocketHandler.MSG_SYSTEM_READY); + break; + case PHASE_BOOT_COMPLETED: + mHandler.sendEmptyMessage(PocketHandler.MSG_SYSTEM_BOOTED); + break; + default: + Slog.w(TAG, "Un-handled boot phase:" + phase); + break; + } + } + + @Override + public void onStart() { + publishBinderService(Context.POCKET_SERVICE, new PocketServiceWrapper()); + } + + @Override + public void binderDied() { + synchronized (mCallbacks) { + mProximityState = PROXIMITY_UNKNOWN; + int callbacksSize = mCallbacks.size(); + for (int i = callbacksSize - 1; i >= 0; i--) { + if (mCallbacks.get(i) != null) { + try { + mCallbacks.get(i).onStateChanged(false, PocketManager.REASON_RESET); + } catch (DeadObjectException e) { + Slog.w(TAG, "Death object while invoking sendPocketState: ", e); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke sendPocketState: ", e); + } + } + } + mCallbacks.clear(); + } + unregisterSensorListeners(); + mObserver.unregister(); + } + + private final class PocketServiceWrapper extends IPocketService.Stub { + + @Override // Binder call + public void addCallback(final IPocketCallback callback) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_ADD_CALLBACK; + msg.obj = callback; + mHandler.sendMessage(msg); + } + + @Override // Binder call + public void removeCallback(final IPocketCallback callback) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_REMOVE_CALLBACK; + msg.obj = callback; + mHandler.sendMessage(msg); + } + + @Override // Binder call + public void onInteractiveChanged(final boolean interactive) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_INTERACTIVE_CHANGED; + msg.arg1 = interactive ? 1 : 0; + mHandler.sendMessage(msg); + } + + @Override // Binder call + public void setListeningExternal(final boolean listen) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_SET_LISTEN_EXTERNAL; + msg.arg1 = listen ? 1 : 0; + mHandler.sendMessage(msg); + } + + @Override // Binder call + public boolean isDeviceInPocket() { + final long ident = Binder.clearCallingIdentity(); + try { + if (!mSystemReady || !mSystemBooted) { + return false; + } + return PocketService.this.isDeviceInPocket(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + public void setPocketLockVisible(final boolean visible) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_SET_POCKET_LOCK_VISIBLE; + msg.arg1 = visible ? 1 : 0; + mHandler.sendMessage(msg); + } + + @Override // Binder call + public boolean isPocketLockVisible() { + final long ident = Binder.clearCallingIdentity(); + try { + if (!mSystemReady || !mSystemBooted) { + return false; + } + return PocketService.this.isPocketLockVisible(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump Pocket from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + final long ident = Binder.clearCallingIdentity(); + try { + dumpInternal(pw); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + } + + private final SensorEventListener mProximityListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent sensorEvent) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_SENSOR_EVENT_PROXIMITY; + msg.obj = sensorEvent; + mHandler.sendMessage(msg); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int i) { } + }; + + private final SensorEventListener mLightListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent sensorEvent) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_SENSOR_EVENT_LIGHT; + msg.obj = sensorEvent; + mHandler.sendMessage(msg); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int i) { } + }; + + private final SensorEventListener mVendorSensorListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent sensorEvent) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_SENSOR_EVENT_VENDOR; + msg.obj = sensorEvent; + mHandler.sendMessage(msg); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int i) { } + }; + + private boolean isDeviceInPocket() { + if (!mSupportedByDevice){ + return false; + } + + if (mVendorSensorState != VENDOR_SENSOR_UNKNOWN) { + return mVendorSensorState == VENDOR_SENSOR_IN_POCKET; + } + + if (mLightState != LIGHT_UNKNOWN) { + return mProximityState == PROXIMITY_POSITIVE + && mLightState == LIGHT_POCKET; + } + return mProximityState == PROXIMITY_POSITIVE; + } + + private void setEnabled(boolean enabled) { + if (!mSupportedByDevice){ + return; + } + if (enabled != mEnabled) { + mEnabled = enabled; + mHandler.removeCallbacksAndMessages(null); + update(); + } + } + + private void update() { + if (!mSupportedByDevice){ + return; + } + if (!mEnabled || mInteractive) { + if (mEnabled && isDeviceInPocket()) { + // if device is judged to be in pocket while switching + // to interactive state, we need to keep monitoring. + return; + } + unregisterSensorListeners(); + } else { + mHandler.removeMessages(PocketHandler.MSG_UNREGISTER_TIMEOUT); + registerSensorListeners(); + } + } + + private void registerSensorListeners() { + if (!mSupportedByDevice){ + return; + } + startListeningForVendorSensor(); + startListeningForProximity(); + startListeningForLight(); + } + + private void unregisterSensorListeners() { + if (!mSupportedByDevice){ + return; + } + stopListeningForVendorSensor(); + stopListeningForProximity(); + stopListeningForLight(); + } + + private void startListeningForVendorSensor() { + if (DEBUG) { + Log.d(TAG, "startListeningForVendorSensor()"); + } + + if (mVendorSensor == null) { + Log.d(TAG, "Cannot detect Vendor pocket sensor, sensor is NULL"); + return; + } + + if (!mVendorSensorRegistered) { + mSensorManager.registerListener(mVendorSensorListener, mVendorSensor, + SensorManager.SENSOR_DELAY_NORMAL, mHandler); + mVendorSensorRegistered = true; + } + } + + private void stopListeningForVendorSensor() { + if (DEBUG) { + Log.d(TAG, "stopListeningForVendorSensor()"); + } + + if (mVendorSensorRegistered) { + mVendorSensorState = mLastVendorSensorState = VENDOR_SENSOR_UNKNOWN; + mSensorManager.unregisterListener(mVendorSensorListener); + mVendorSensorRegistered = false; + } + } + + private void startListeningForProximity() { + + if (mVendorSensor != null) { + return; + } + + if (DEBUG) { + Log.d(TAG, "startListeningForProximity()"); + } + + if (!PocketConstants.ENABLE_PROXIMITY_JUDGE) { + return; + } + + if (mProximitySensor == null) { + Log.d(TAG, "Cannot detect proximity sensor, sensor is NULL"); + return; + } + + if (!mProximityRegistered) { + mSensorManager.registerListener(mProximityListener, mProximitySensor, + PROXIMITY_SENSOR_DELAY, mHandler); + mProximityRegistered = true; + } + } + + private void stopListeningForProximity() { + if (DEBUG) { + Log.d(TAG, "startListeningForProximity()"); + } + + if (mProximityRegistered) { + mLastProximityState = mProximityState = PROXIMITY_UNKNOWN; + mSensorManager.unregisterListener(mProximityListener); + mProximityRegistered = false; + } + } + + private void startListeningForLight() { + boolean mUseLightSensor = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_pocketUseLightSensor); + + if (mVendorSensor != null) { + return; + } + + if (DEBUG) { + Log.d(TAG, "startListeningForLight()"); + } + + if (!mUseLightSensor) { + return; + } + + if (mLightSensor == null) { + Log.d(TAG, "Cannot detect light sensor, sensor is NULL"); + return; + } + + if (!mLightRegistered) { + mSensorManager.registerListener(mLightListener, mLightSensor, + LIGHT_SENSOR_DELAY, mHandler); + mLightRegistered = true; + } + } + + private void stopListeningForLight() { + if (DEBUG) { + Log.d(TAG, "stopListeningForLight()"); + } + + if (mLightRegistered) { + mLightState = mLastLightState = LIGHT_UNKNOWN; + mSensorManager.unregisterListener(mLightListener); + mLightRegistered = false; + } + } + + private void handleSystemReady() { + if (DEBUG) { + Log.d(TAG, "onBootPhase(): PHASE_SYSTEM_SERVICES_READY"); + Log.d(TAG, "onBootPhase(): VENDOR_SENSOR: " + mVendorPocketSensor); + } + mSystemReady = true; + + if (mPending) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_INTERACTIVE_CHANGED; + msg.arg1 = mInteractive ? 1 : 0; + mHandler.sendMessage(msg); + mPending = false; + } + } + + private void handleSystemBooted() { + if (DEBUG) { + Log.d(TAG, "onBootPhase(): PHASE_BOOT_COMPLETED"); + } + mSystemBooted = true; + if (mPending) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_INTERACTIVE_CHANGED; + msg.arg1 = mInteractive ? 1 : 0; + mHandler.sendMessage(msg); + mPending = false; + } + } + + private void handleDispatchCallbacks() { + synchronized (mCallbacks) { + final int N = mCallbacks.size(); + boolean cleanup = false; + for (int i = 0; i < N; i++) { + final IPocketCallback callback = mCallbacks.get(i); + try { + if (callback != null) { + callback.onStateChanged(isDeviceInPocket(), PocketManager.REASON_SENSOR); + } else { + cleanup = true; + } + } catch (RemoteException e) { + cleanup = true; + } + } + if (cleanup) { + cleanUpCallbacksLocked(null); + } + } + } + + private void cleanUpCallbacksLocked(IPocketCallback callback) { + synchronized (mCallbacks) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + IPocketCallback found = mCallbacks.get(i); + if (found == null || found == callback) { + mCallbacks.remove(i); + } + } + } + } + + private void handleSetPocketLockVisible(boolean visible) { + mPocketLockVisible = visible; + } + + private boolean isPocketLockVisible() { + return mPocketLockVisible; + } + + private void handleSetListeningExternal(boolean listen) { + if (listen) { + // should prevent external processes to register while interactive, + // while they are allowed to stop listening in any case as for example + // coming pocket lock will need to. + if (!mInteractive) { + registerSensorListeners(); + } + } else { + mHandler.removeCallbacksAndMessages(null); + unregisterSensorListeners(); + } + dispatchCallbacks(); + } + + private void handleAddCallback(IPocketCallback callback) { + synchronized (mCallbacks) { + if (!mCallbacks.contains(callback)) { + mCallbacks.add(callback); + } + } + } + + private void handleRemoveCallback(IPocketCallback callback) { + synchronized (mCallbacks) { + if (mCallbacks.contains(callback)) { + mCallbacks.remove(callback); + } + } + } + + private void handleInteractiveChanged(boolean interactive) { + // always update interactive state. + mInteractive = interactive; + + if (mPending) { + // working on it, waiting for proper system conditions. + return; + } else if (!mPending && (!mSystemBooted || !mSystemReady)) { + // we ain't ready, postpone till system is both booted AND ready. + mPending = true; + return; + } + + update(); + } + + private void handleVendorSensorEvent(SensorEvent sensorEvent) { + final boolean isDeviceInPocket = isDeviceInPocket(); + + mLastVendorSensorState = mVendorSensorState; + + if (DEBUG) { + final String sensorEventToString = sensorEvent != null ? sensorEvent.toString() : "NULL"; + Log.d(TAG, "VENDOR_SENSOR: onSensorChanged(), sensorEvent =" + sensorEventToString); + } + + try { + if (sensorEvent == null) { + if (DEBUG) Log.d(TAG, "Event is null!"); + mVendorSensorState = VENDOR_SENSOR_UNKNOWN; + } else if (sensorEvent.values == null || sensorEvent.values.length == 0) { + if (DEBUG) Log.d(TAG, "Event has no values! event.values null ? " + (sensorEvent.values == null)); + mVendorSensorState = VENDOR_SENSOR_UNKNOWN; + } else { + final boolean isVendorPocket = sensorEvent.values[0] == 1.0; + if (DEBUG) { + final long time = SystemClock.uptimeMillis(); + Log.d(TAG, "Event: time=" + time + ", value=" + sensorEvent.values[0] + + ", isInPocket=" + isVendorPocket); + } + mVendorSensorState = isVendorPocket ? VENDOR_SENSOR_IN_POCKET : VENDOR_SENSOR_UNKNOWN; + } + } catch (NullPointerException e) { + Log.e(TAG, "Event: something went wrong, exception caught, e = " + e); + mVendorSensorState = VENDOR_SENSOR_UNKNOWN; + } finally { + if (isDeviceInPocket != isDeviceInPocket()) { + dispatchCallbacks(); + } + } + } + + private void handleLightSensorEvent(SensorEvent sensorEvent) { + final boolean isDeviceInPocket = isDeviceInPocket(); + + mLastLightState = mLightState; + + if (DEBUG) { + final String sensorEventToString = sensorEvent != null ? sensorEvent.toString() : "NULL"; + Log.d(TAG, "LIGHT_SENSOR: onSensorChanged(), sensorEvent =" + sensorEventToString); + } + + try { + if (sensorEvent == null) { + if (DEBUG) Log.d(TAG, "Event is null!"); + mLightState = LIGHT_UNKNOWN; + } else if (sensorEvent.values == null || sensorEvent.values.length == 0) { + if (DEBUG) Log.d(TAG, "Event has no values! event.values null ? " + (sensorEvent.values == null)); + mLightState = LIGHT_UNKNOWN; + } else { + final float value = sensorEvent.values[0]; + final boolean isPoor = value >= 0 + && value <= POCKET_LIGHT_MAX_THRESHOLD; + if (DEBUG) { + final long time = SystemClock.uptimeMillis(); + Log.d(TAG, "Event: time= " + time + ", value=" + value + + ", maxRange=" + mLightMaxRange + ", isPoor=" + isPoor); + } + mLightState = isPoor ? LIGHT_POCKET : LIGHT_AMBIENT; + } + } catch (NullPointerException e) { + Log.e(TAG, "Event: something went wrong, exception caught, e = " + e); + mLightState = LIGHT_UNKNOWN; + } finally { + if (isDeviceInPocket != isDeviceInPocket()) { + dispatchCallbacks(); + } + } + } + + private void handleProximitySensorEvent(SensorEvent sensorEvent) { + final boolean isDeviceInPocket = isDeviceInPocket(); + + mLastProximityState = mProximityState; + + if (DEBUG) { + final String sensorEventToString = sensorEvent != null ? sensorEvent.toString() : "NULL"; + Log.d(TAG, "PROXIMITY_SENSOR: onSensorChanged(), sensorEvent =" + sensorEventToString); + } + + try { + if (sensorEvent == null) { + if (DEBUG) Log.d(TAG, "Event is null!"); + mProximityState = PROXIMITY_UNKNOWN; + } else if (sensorEvent.values == null || sensorEvent.values.length == 0) { + if (DEBUG) Log.d(TAG, "Event has no values! event.values null ? " + (sensorEvent.values == null)); + mProximityState = PROXIMITY_UNKNOWN; + } else { + final float value = sensorEvent.values[0]; + final boolean isPositive = sensorEvent.values[0] < mProximityMaxRange; + if (DEBUG) { + final long time = SystemClock.uptimeMillis(); + Log.d(TAG, "Event: time=" + time + ", value=" + value + + ", maxRange=" + mProximityMaxRange + ", isPositive=" + isPositive); + } + mProximityState = isPositive ? PROXIMITY_POSITIVE : PROXIMITY_NEGATIVE; + } + } catch (NullPointerException e) { + Log.e(TAG, "Event: something went wrong, exception caught, e = " + e); + mProximityState = PROXIMITY_UNKNOWN; + } finally { + if (isDeviceInPocket != isDeviceInPocket()) { + dispatchCallbacks(); + } + } + } + + private void handleUnregisterTimeout() { + mHandler.removeCallbacksAndMessages(null); + unregisterSensorListeners(); + } + + private static Sensor getSensor(SensorManager sm, String type) { + for (Sensor sensor : sm.getSensorList(Sensor.TYPE_ALL)) { + if (type.equals(sensor.getStringType())) { + return sensor; + } + } + return null; + } + + private void dispatchCallbacks() { + final boolean isDeviceInPocket = isDeviceInPocket(); + if (mInteractive) { + if (!isDeviceInPocket) { + mHandler.sendEmptyMessageDelayed(PocketHandler.MSG_UNREGISTER_TIMEOUT, 5000 /* ms */); + } else { + mHandler.removeMessages(PocketHandler.MSG_UNREGISTER_TIMEOUT); + } + } + mHandler.removeMessages(PocketHandler.MSG_DISPATCH_CALLBACKS); + mHandler.sendEmptyMessage(PocketHandler.MSG_DISPATCH_CALLBACKS); + } + + private void dumpInternal(PrintWriter pw) { + JSONObject dump = new JSONObject(); + try { + dump.put("service", "POCKET"); + dump.put("enabled", mEnabled); + dump.put("isDeviceInPocket", isDeviceInPocket()); + dump.put("interactive", mInteractive); + dump.put("proximityState", mProximityState); + dump.put("lastProximityState", mLastProximityState); + dump.put("proximityRegistered", mProximityRegistered); + dump.put("proximityMaxRange", mProximityMaxRange); + dump.put("lightState", mLightState); + dump.put("lastLightState", mLastLightState); + dump.put("lightRegistered", mLightRegistered); + dump.put("lightMaxRange", mLightMaxRange); + dump.put("VendorSensorState", mVendorSensorState); + dump.put("lastVendorSensorState", mLastVendorSensorState); + dump.put("VendorSensorRegistered", mVendorSensorRegistered); + } catch (JSONException e) { + Slog.e(TAG, "dump formatting failure", e); + } finally { + pw.println(dump); + } + } +} diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 35ca32dc70c1..fa9a56b190a7 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -158,6 +158,8 @@ import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.DeviceConfig; +import android.pocket.IPocketCallback; +import android.pocket.PocketManager; import android.provider.MediaStore; import android.provider.Settings; import android.service.dreams.DreamManagerInternal; @@ -219,6 +221,7 @@ import com.android.server.policy.keyguard.KeyguardServiceDelegate; import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener; import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback; +import com.android.server.policy.pocket.PocketLock; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.vr.VrManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; @@ -282,6 +285,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final int LONG_PRESS_POWER_GO_TO_VOICE_ASSIST = 4; static final int LONG_PRESS_POWER_ASSISTANT = 5; // Settings.Secure.ASSISTANT static final int LONG_PRESS_POWER_TORCH = 10; + static final int LONG_PRESS_POWER_HIDE_POCKET_LOCK = 7; // must match: config_veryLongPresOnPowerBehavior in config.xml static final int VERY_LONG_PRESS_POWER_NOTHING = 0; @@ -691,6 +695,30 @@ public void onTorchModeUnavailable(String cameraId) { }; private boolean mLockNowPending = false; + private PocketManager mPocketManager; + private PocketLock mPocketLock; + private boolean mPocketLockShowing; + private boolean mIsDeviceInPocket; + private final IPocketCallback mPocketCallback = new IPocketCallback.Stub() { + + @Override + public void onStateChanged(boolean isDeviceInPocket, int reason) { + boolean wasDeviceInPocket = mIsDeviceInPocket; + if (reason == PocketManager.REASON_SENSOR) { + mIsDeviceInPocket = isDeviceInPocket; + } else { + mIsDeviceInPocket = false; + } + if (wasDeviceInPocket != mIsDeviceInPocket) { + handleDevicePocketStateChanged(); + //if (mKeyHandler != null) { + //mKeyHandler.setIsInPocket(mIsDeviceInPocket); + //} + } + } + + }; + private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3; private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4; private static final int MSG_KEYGUARD_DRAWN_COMPLETE = 5; @@ -1083,6 +1111,9 @@ private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { mPowerKeyWakeLock.acquire(); } + // Still allow muting call with power button press. + boolean blockInputs = mIsDeviceInPocket && (!interactive || mPocketLockShowing); + // Cancel multi-press detection timeout. if (mPowerKeyPressCounter != 0) { mHandler.removeMessages(MSG_POWER_DELAYED_PRESS); @@ -1090,15 +1121,20 @@ private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { mWindowManagerFuncs.onPowerKeyDown(interactive); - // Latch power key state to detect screenshot chord. - if (interactive && !mScreenshotChordPowerKeyTriggered - && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { - mScreenshotChordPowerKeyTriggered = true; - mScreenshotChordPowerKeyTime = event.getDownTime(); - interceptScreenshotChord(); - interceptRingerToggleChord(); + if (!blockInputs) { + // Latch power key state to detect screenshot chord. + if (interactive && !mScreenshotChordPowerKeyTriggered + && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { + mScreenshotChordPowerKeyTriggered = true; + mScreenshotChordPowerKeyTime = event.getDownTime(); + interceptScreenshotChord(); + interceptRingerToggleChord(); + } } + // Abort possibly stuck animations. + mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe); + // Stop ringing or end call if configured to do so when power is pressed. TelecomManager telecomManager = getTelecommService(); boolean hungUp = false; @@ -1107,7 +1143,7 @@ private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { // Pressing Power while there's a ringing incoming // call should silence the ringer. telecomManager.silenceRinger(); - } else if ((mIncallPowerBehavior + } else if (!blockInputs && (mIncallPowerBehavior & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0 && telecomManager.isInCall() && interactive) { // Otherwise, if "Power button ends call" is enabled, @@ -1118,14 +1154,16 @@ private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event); - GestureLauncherService gestureService = LocalServices.getService( - GestureLauncherService.class); boolean gesturedServiceIntercepted = false; - if (gestureService != null) { - gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive, - mTmpBoolean); - if (mTmpBoolean.value && mRequestedOrGoingToSleep) { - mCameraGestureTriggeredDuringGoingToSleep = true; + if (!blockInputs) { + GestureLauncherService gestureService = LocalServices.getService( + GestureLauncherService.class); + if (gestureService != null) { + gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive, + mTmpBoolean); + if (mTmpBoolean.value && mRequestedOrGoingToSleep) { + mCameraGestureTriggeredDuringGoingToSleep = true; + } } } @@ -1483,6 +1521,13 @@ private void powerLongPress() { msg.setAsynchronous(true); msg.sendToTarget(); break; + case LONG_PRESS_POWER_HIDE_POCKET_LOCK: + mPowerKeyHandled = true; + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, + "Power - Long Press - Hide Pocket Lock"); + hidePocketLock(true); + mPocketManager.setListeningExternal(false); + break; } } @@ -1539,6 +1584,9 @@ private int getResolvedLongPressOnPowerBehavior() { if ((mTorchActionMode == 1) && (!isScreenOn() || mTorchEnabled)) { return LONG_PRESS_POWER_TORCH; } + if (mPocketLockShowing) { + return LONG_PRESS_POWER_HIDE_POCKET_LOCK; + } return mLongPressOnPowerBehavior; } @@ -1663,7 +1711,9 @@ public void setScreenshotSource(int screenshotSource) { @Override public void run() { - mDefaultDisplayPolicy.takeScreenshot(mScreenshotType, mScreenshotSource); + if (!mPocketLockShowing){ + mDefaultDisplayPolicy.takeScreenshot(mScreenshotType, mScreenshotSource); + } } } @@ -2099,7 +2149,9 @@ public void init(Context context, IWindowManager windowManager, mOPGestures = new OPGesturesListener(context, new OPGesturesListener.Callbacks() { @Override public void onSwipeThreeFinger() { - mHandler.post(mScreenshotRunnable); + if (!mPocketLockShowing) { + mHandler.post(mScreenshotRunnable); + } } }); mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler); @@ -4231,6 +4283,23 @@ public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { return 0; } + // Pre-basic policy based on interactive and pocket lock state. + if (mIsDeviceInPocket && (!interactive || mPocketLockShowing)) { + if (keyCode != KeyEvent.KEYCODE_POWER && + keyCode != KeyEvent.KEYCODE_VOLUME_UP && + keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && + keyCode != KeyEvent.KEYCODE_MEDIA_PLAY && + keyCode != KeyEvent.KEYCODE_MEDIA_PAUSE && + keyCode != KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE && + keyCode != KeyEvent.KEYCODE_HEADSETHOOK && + keyCode != KeyEvent.KEYCODE_MEDIA_STOP && + keyCode != KeyEvent.KEYCODE_MEDIA_NEXT && + keyCode != KeyEvent.KEYCODE_MEDIA_PREVIOUS && + keyCode != KeyEvent.KEYCODE_VOLUME_MUTE) { + return 0; + } + } + // Basic policy based on interactive state. final boolean isVolumeRockerWake = !isScreenOn() && mVolumeRockerWake @@ -5160,6 +5229,9 @@ public void startedGoingToSleep(int why) { if (mKeyguardDelegate != null) { mKeyguardDelegate.onStartedGoingToSleep(why); } + if (mPocketManager != null) { + mPocketManager.onInteractiveChanged(false); + } } // Called on the PowerManager's Notifier thread. @@ -5218,6 +5290,10 @@ public void startedWakingUp(@OnReason int why) { if (mKeyguardDelegate != null) { mKeyguardDelegate.onStartedWakingUp(); } + + if (mPocketManager != null) { + mPocketManager.onInteractiveChanged(true); + } } // Called on the PowerManager's Notifier thread. @@ -5407,6 +5483,72 @@ private void finishScreenTurningOn() { } } + /** + * Perform operations if needed on pocket mode state changed. + * @see com.android.server.pocket.PocketService + * @see PocketLock + * @see this.mPocketCallback; + * @author Carlo Savignano + */ + private void handleDevicePocketStateChanged() { + final boolean interactive = mPowerManager.isInteractive(); + if (mIsDeviceInPocket) { + showPocketLock(interactive); + } else { + hidePocketLock(interactive); + } + } + + /** + * Check if we can show pocket lock once requested. + * @see com.android.server.pocket.PocketService + * @see PocketLock + * @see this.mPocketCallback; + * @author Carlo Savignano + */ + private void showPocketLock(boolean animate) { + if (!mSystemReady || !mSystemBooted || !mKeyguardDrawnOnce + || mPocketLock == null || mPocketLockShowing) { + return; + } + + if (mPowerManager.isInteractive() && !isKeyguardShowingAndNotOccluded()){ + return; + } + + if (DEBUG_INPUT) { + Log.d(TAG, "showPocketLock, animate=" + animate); + } + + mPocketLock.show(animate); + mPocketLockShowing = true; + + mPocketManager.setPocketLockVisible(true); + } + + /** + * Check if we can hide pocket lock once requested. + * @see com.android.server.pocket.PocketService + * @see PocketLock + * @see this.mPocketCallback; + * @author Carlo Savignano + */ + private void hidePocketLock(boolean animate) { + if (!mSystemReady || !mSystemBooted || !mKeyguardDrawnOnce + || mPocketLock == null || !mPocketLockShowing) { + return; + } + + if (DEBUG_INPUT) { + Log.d(TAG, "hidePocketLock, animate=" + animate); + } + + mPocketLock.hide(animate); + mPocketLockShowing = false; + + mPocketManager.setPocketLockVisible(false); + } + private void handleHideBootMessage() { synchronized (mLock) { if (!mKeyguardDrawnOnce) { @@ -5567,6 +5709,10 @@ public void systemReady() { // So it is better not to bind keyguard here. mKeyguardDelegate.onSystemReady(); + mPocketManager = (PocketManager) mContext.getSystemService(Context.POCKET_SERVICE); + mPocketManager.addCallback(mPocketCallback); + mPocketLock = new PocketLock(mContext); + mVrManagerInternal = LocalServices.getService(VrManagerInternal.class); if (mVrManagerInternal != null) { mVrManagerInternal.addPersistentVrModeStateListener(mPersistentVrModeListener); diff --git a/services/core/java/com/android/server/policy/pocket/PocketLock.java b/services/core/java/com/android/server/policy/pocket/PocketLock.java new file mode 100644 index 000000000000..09e77c195d3c --- /dev/null +++ b/services/core/java/com/android/server/policy/pocket/PocketLock.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2016 The ParanoidAndroid Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.policy.pocket; + +import android.animation.Animator; +import android.content.Context; +import android.graphics.PixelFormat; +import android.os.Handler; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; + +/** + * This class provides a fullscreen overlays view, displaying itself + * even on top of lock screen. While this view is displaying touch + * inputs are not passed to the the views below. + * @see android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; + * @author Carlo Savignano + */ +public class PocketLock { + + private final Context mContext; + private WindowManager mWindowManager; + private WindowManager.LayoutParams mLayoutParams; + private Handler mHandler; + private View mView; + private View mHintContainer; + + private boolean mAttached; + private boolean mAnimating; + + /** + * Creates pocket lock objects, inflate view and set layout parameters. + * @param context + */ + public PocketLock(Context context) { + mContext = context; + mHandler = new Handler(); + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + mLayoutParams = getLayoutParams(); + mView = LayoutInflater.from(mContext).inflate( + com.android.internal.R.layout.pocket_lock_view_layout, null); + } + + public void show(final boolean animate) { + final Runnable r = new Runnable() { + @Override + public void run() { + if (mAttached) { + return; + } + + if (mAnimating) { + mView.animate().cancel(); + } + + if (animate) { + mView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mView.animate().alpha(1.0f).setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + mAnimating = true; + } + + @Override + public void onAnimationEnd(Animator animator) { + mView.setLayerType(View.LAYER_TYPE_NONE, null); + mAnimating = false; + } + + @Override + public void onAnimationCancel(Animator animator) { + } + + @Override + public void onAnimationRepeat(Animator animator) { + } + }).withStartAction(new Runnable() { + @Override + public void run() { + mView.setAlpha(0.0f); + mView.setVisibility(View.VISIBLE); + addView(); + } + }).start(); + } else { + mView.setVisibility(View.VISIBLE); + mView.setAlpha(1.0f); + addView(); + } + } + }; + + mHandler.post(r); + } + + public void hide(final boolean animate) { + final Runnable r = new Runnable() { + @Override + public void run() { + if (!mAttached) { + return; + } + + if (mAnimating) { + mView.animate().cancel(); + } + + if (animate) { + mView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mView.animate().alpha(0.0f).setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + mAnimating = true; + } + + @Override + public void onAnimationEnd(Animator animator) { + mView.setVisibility(View.GONE); + mView.setLayerType(View.LAYER_TYPE_NONE, null); + mAnimating = false; + removeView(); + } + + @Override + public void onAnimationCancel(Animator animator) { + } + + @Override + public void onAnimationRepeat(Animator animator) { + } + }).start(); + } else { + mView.setVisibility(View.GONE); + mView.setAlpha(0.0f); + removeView(); + } + } + }; + + mHandler.post(r); + } + + private void addView() { + if (mWindowManager != null && !mAttached) { + mWindowManager.addView(mView, mLayoutParams); + mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_POCKET_LOCK); + mAttached = true; + } + } + + private void removeView() { + if (mWindowManager != null && mAttached) { + mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + mWindowManager.removeView(mView); + mAnimating = false; + mAttached = false; + } + } + + private WindowManager.LayoutParams getLayoutParams() { + mLayoutParams = new WindowManager.LayoutParams(); + mLayoutParams.format = PixelFormat.TRANSLUCENT; + mLayoutParams.height = WindowManager.LayoutParams.MATCH_PARENT; + mLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT; + mLayoutParams.gravity = Gravity.CENTER; + mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; + mLayoutParams.layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + mLayoutParams.flags = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + | WindowManager.LayoutParams.FLAG_FULLSCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; + return mLayoutParams; + } + +} diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index bef45439fcdb..60fb3a5f0282 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -3917,6 +3917,7 @@ private Pair updateSystemBarsLw(WindowState win, int oldVi final boolean immersive = (vis & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0; immersiveSticky = (vis & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0; final boolean navAllowedHidden = immersive || immersiveSticky; + final boolean isPocketLock = (vis & View.SYSTEM_UI_FLAG_POCKET_LOCK) != 0; if (hideNavBarSysui && !navAllowedHidden && mService.mPolicy.getWindowLayerLw(win) @@ -3935,7 +3936,7 @@ private Pair updateSystemBarsLw(WindowState win, int oldVi if (oldImmersiveMode != newImmersiveMode) { mLastImmersiveMode = newImmersiveMode; final String pkg = win.getOwningPackage(); - mImmersiveModeConfirmation.immersiveModeChangedLw(pkg, newImmersiveMode, + mImmersiveModeConfirmation.immersiveModeChangedLw(pkg, isPocketLock ? false : newImmersiveMode, mService.mPolicy.isUserSetupComplete(), isNavBarEmpty(win.getSystemUiVisibility())); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3d5230bff8f6..9448dbfbbb8d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -136,6 +136,8 @@ import com.android.server.os.DeviceIdentifiersPolicyService; import com.android.server.os.SchedulingPolicyService; import com.android.server.people.PeopleService; +import com.android.server.pocket.PocketService; +import com.android.server.pocket.PocketBridgeService; import com.android.server.pm.BackgroundDexOptService; import com.android.server.pm.CrossProfileAppsService; import com.android.server.pm.DataLoaderManagerService; @@ -2047,6 +2049,18 @@ private void startOtherServices(@NonNull TimingsTraceAndSlog t) { mSystemServiceManager.startService(LineageHardwareService.class); t.traceEnd(); } + + // Pocket + t.traceBegin("StartPocketService"); + mSystemServiceManager.startService(PocketService.class); + t.traceEnd(); + + if (!context.getResources().getString( + com.android.internal.R.string.config_pocketBridgeSysfsInpocket).isEmpty()) { + t.traceBegin("StartPocketBridgeService"); + mSystemServiceManager.startService(PocketBridgeService.class); + t.traceEnd(); + } } if (!isWatch) {