diff --git a/Android.bp b/Android.bp index 33f803d040a..846126a0b74 100755 --- a/Android.bp +++ b/Android.bp @@ -64,6 +64,8 @@ java_defaults { "core/java/android/app/IAlarmListener.aidl", "core/java/android/app/IAlarmManager.aidl", "core/java/android/app/IAppTask.aidl", + "core/java/android/app/IAppLockCallback.aidl", + "core/java/android/app/IAppLockService.aidl", "core/java/android/app/IApplicationThread.aidl", "core/java/android/app/IAssistDataReceiver.aidl", "core/java/android/app/ITaskStackListener.aidl", @@ -695,6 +697,12 @@ java_defaults { ":platform-properties", ":framework-statslog-gen", + + // Long screenshot + "core/java/com/android/internal/custom/longshot/ILongScreenshot.aidl", + "core/java/com/android/internal/custom/longshot/ILongScreenshotCallback.aidl", + "core/java/com/android/internal/custom/longshot/ILongScreenshotListener.aidl", + "core/java/com/android/internal/custom/longshot/ILongScreenshotManager.aidl", ], aidl: { diff --git a/core/java/android/app/AppLockManager.java b/core/java/android/app/AppLockManager.java new file mode 100644 index 00000000000..8dd25e5c34e --- /dev/null +++ b/core/java/android/app/AppLockManager.java @@ -0,0 +1,143 @@ +/** + * Copyright (C) 2017-2020 Paranoid Android + * + * 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.app; + +import android.annotation.SystemService; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceManager.ServiceNotFoundException; + +import java.util.List; + +/** + * @author Anas Karbila + * @author Rituj Beniwal + * @hide + */ +@SystemService(Context.APPLOCK_SERVICE) +public class AppLockManager { + + private static final String TAG = "AppLockManager"; + + private IAppLockService mService; + + public AppLockManager(IAppLockService service) { + mService = service; + } + + public void addAppToList(String packageName) { + try { + mService.addAppToList(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public void removeAppFromList(String packageName) { + try { + mService.removeAppFromList(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public boolean isAppLocked(String packageName) { + try { + return mService.isAppLocked(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public boolean isAppOpen(String packageName) { + try { + return mService.isAppOpen(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public void setShowOnlyOnWake(boolean showOnce) { + try { + mService.setShowOnlyOnWake(showOnce); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public boolean getShowOnlyOnWake() { + try { + return mService.getShowOnlyOnWake(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public int getLockedAppsCount() { + try { + return mService.getLockedAppsCount(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public List getLockedPackages() { + try { + return mService.getLockedPackages(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public boolean getAppNotificationHide(String packageName) { + try { + return mService.getAppNotificationHide(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public void setAppNotificationHide(String packageName, boolean hide) { + try { + mService.setAppNotificationHide(packageName, hide); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public void addAppLockCallback(IAppLockCallback c) { + try { + mService.addAppLockCallback(c); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public void removeAppLockCallback(IAppLockCallback c) { + try { + mService.removeAppLockCallback(c); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public abstract static class AppLockCallback extends IAppLockCallback.Stub { + @Override + public abstract void onAppStateChanged(String pkg); + }; +} diff --git a/core/java/android/app/IAppLockCallback.aidl b/core/java/android/app/IAppLockCallback.aidl new file mode 100644 index 00000000000..b03d8b65181 --- /dev/null +++ b/core/java/android/app/IAppLockCallback.aidl @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2017-2020 Paranoid Android + * + * 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.app; + +/** @hide */ +oneway interface IAppLockCallback { + + void onAppStateChanged(String packageName); +} \ No newline at end of file diff --git a/core/java/android/app/IAppLockService.aidl b/core/java/android/app/IAppLockService.aidl new file mode 100644 index 00000000000..3a5c9ab8a36 --- /dev/null +++ b/core/java/android/app/IAppLockService.aidl @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2017-2020 Paranoid Android + * + * 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.app; + +import android.app.IAppLockCallback; + +/** @hide */ +interface IAppLockService { + + void addAppToList(in String packageName); + + void removeAppFromList(in String packageName); + + boolean isAppLocked(in String packageName); + + boolean isAppOpen(in String packageName); + + void setShowOnlyOnWake(in boolean showOnce); + + boolean getShowOnlyOnWake(); + + int getLockedAppsCount(); + + List getLockedPackages(); + + boolean getAppNotificationHide(in String packageName); + + void setAppNotificationHide(in String packageName, in boolean hide); + + void addAppLockCallback(IAppLockCallback callback); + + void removeAppLockCallback(IAppLockCallback callback); +} diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 136e932a3eb..0189f02e032 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -193,6 +193,7 @@ import com.android.internal.app.IBatteryStats; import com.android.internal.app.ISoundTriggerService; import com.android.internal.appwidget.IAppWidgetService; +import com.android.internal.custom.longshot.LongScreenshotManager; import com.android.internal.net.INetworkWatchlistManager; import com.android.internal.os.IDropBoxManagerService; import com.android.internal.policy.PhoneLayoutInflater; @@ -958,6 +959,15 @@ public BiometricManager createService(ContextImpl ctx) } }); + registerService(Context.APPLOCK_SERVICE, AppLockManager.class, + new CachedServiceFetcher() { + @Override + public AppLockManager createService(ContextImpl ctx) throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow(Context.APPLOCK_SERVICE); + IAppLockService service = IAppLockService.Stub.asInterface(b); + return new AppLockManager(service); + }}); + registerService(Context.TV_INPUT_SERVICE, TvInputManager.class, new CachedServiceFetcher() { @Override @@ -1300,6 +1310,14 @@ public DynamicSystemManager createService(ContextImpl ctx) return new DynamicSystemManager( IDynamicSystemService.Stub.asInterface(b)); }}); + + registerService(Context.LONGSCREENSHOT_SERVICE, LongScreenshotManager.class, + new CachedServiceFetcher() { + @Override + public LongScreenshotManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + return LongScreenshotManager.getInstance(); + }}); //CHECKSTYLE:ON IndentationCheck } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 414cc39f531..2f186b4420c 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4685,6 +4685,22 @@ public abstract boolean startInstrumentation(@NonNull ComponentName className, */ public static final String DYNAMIC_SYSTEM_SERVICE = "dynamic_system"; + /** + * Long screenshot + * @hide + */ + public static final String LONGSCREENSHOT_SERVICE = "longshot"; + + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.app.AppLockManager} for accessing and setting locked apps state. + * + * @hide + * @see #getSystemService + * @see android.app.AppLockManager + */ + public static final String APPLOCK_SERVICE = "applock"; + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 1142a07bc66..8b3748cb2ed 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -58,6 +58,10 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * @hide */ public static final String KEY_USE_DEFAULT_TITLE = "use_default_title"; + /** + * @hide + */ + public static final String KEY_APPLOCK_PKG = "applock_package_name"; /** * @hide */ @@ -160,6 +164,17 @@ public Builder(Context context) { return this; } + /** + * Optional: Show a special dialog for app locker if KEY_APPLOCK_PKG is set + * @param packageName + * @return + * @hide + */ + @NonNull public Builder setApplockPackage(@NonNull CharSequence packageName) { + mBundle.putCharSequence(KEY_APPLOCK_PKG, packageName); + return this; + } + /** * Optional: Set the subtitle to display. * @param subtitle diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 12b285a0f0a..af474910f7e 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -244,6 +244,59 @@ public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSi } } + /** + * Request face authentication enrollment. This call operates the face authentication hardware + * and starts capturing images. Progress will be indicated by callbacks to the + * {@link EnrollmentCallback} object. It terminates when + * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} or + * {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0, at + * which point the object is no longer valid. The operation can be canceled by using the + * provided cancel object. + * + * @param token a unique token provided by a recent creation or verification of device + * credentials (e.g. pin, pattern or password). + * @param cancel an object that can be used to cancel enrollment + * @param flags optional flags + * @param callback an object to receive enrollment events + * @hide + */ + @RequiresPermission(MANAGE_BIOMETRIC) + public void enroll(byte[] token, CancellationSignal cancel, + EnrollmentCallback callback, int[] disabledFeatures) { + if (callback == null) { + throw new IllegalArgumentException("Must supply an enrollment callback"); + } + + if (cancel != null) { + if (cancel.isCanceled()) { + Log.w(TAG, "enrollment already canceled"); + return; + } else { + cancel.setOnCancelListener(new OnEnrollCancelListener()); + } + } + + if (mService != null) { + try { + mEnrollmentCallback = callback; + Trace.beginSection("FaceManager#enroll"); + mService.enrollMoto(mToken, token, mServiceReceiver, + mContext.getOpPackageName(), disabledFeatures); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in enroll: ", e); + if (callback != null) { + // Though this may not be a hardware issue, it will cause apps to give up or + // try again later. + callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE, + getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE, + 0 /* vendorCode */)); + } + } finally { + Trace.endSection(); + } + } + } + /** * Request face authentication enrollment. This call operates the face authentication hardware * and starts capturing images. Progress will be indicated by callbacks to the diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index b6a0afbf716..9aa34d4738d 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -105,4 +105,9 @@ interface IFaceService { void getFeature(int userId, int feature, IFaceServiceReceiver receiver, String opPackageName); void userActivity(); + + // Moto additions + // Start face enrollment + void enrollMoto(IBinder token, in byte [] cryptoToken, IFaceServiceReceiver receiver, + String opPackageName, in int [] disabledFeatures); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index cf9f419ba99..d7d9921e916 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3570,6 +3570,13 @@ public boolean validate(@Nullable String value) { */ public static final int SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1; + /** + * Indicates whether we should only show the app lock view when the device is woken up + * Or always. + * @hide + */ + public static final String APP_LOCK_SHOW_ONLY_ON_WAKE = "app_lock_show_only_on_wake"; + /** * Control whether to enable adaptive sleep mode. * @hide @@ -4535,13 +4542,20 @@ public boolean validate(@Nullable String value) { /** @hide */ private static final Validator DOUBLE_TAP_SLEEP_LOCKSCREEN_VALIDATOR = BOOLEAN_VALIDATOR; - + /** * Disable Screenshot shutter sound * @hide */ public static final String SCREENSHOT_SHUTTER_SOUND = "screenshot_shutter_sound"; + /** + * Screenshod sound enable, This is the noise made when taking a screesnhot + * Defaults to 1 - sounds enabled + * @hide + */ + public static final String SCREENSHOT_SOUND = "screenshot_sound"; + /** @hide */ private static final Validator SCREENSHOT_SHUTTER_SOUND_VALIDATOR = BOOLEAN_VALIDATOR; @@ -4793,6 +4807,27 @@ public boolean validate(@Nullable String value) { GESTURE_PILL_TOGGLE, }; + /** + * @hide + */ + public static final String SMART_CHARGING = "smart_charging"; + + /** + * @hide + */ + public static final String SMART_CHARGING_RESET_STATS = "smart_charging_reset_stats"; + + /** + * @hide + */ + public static final String SMART_CHARGING_LEVEL = "smart_charging_level"; + + /** + * @hide + */ + public static final String SMART_CHARGING_RESUME_LEVEL = "smart_charging_resume_level"; + + /** * Keys we no longer back up under the current schema, but want to continue to * process when restoring historical backup datasets. @@ -8572,6 +8607,20 @@ public boolean validate(@Nullable String value) { private static final Validator CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED_VALIDATOR = BOOLEAN_VALIDATOR; + /** + * Whether the torch launch gesture to long press the power button when the + * screen is off should be enabled. + * + * 0: disabled + * 1: long tap power for torch + * @hide + */ + public static final String TORCH_POWER_BUTTON_GESTURE = + "torch_power_button_gesture"; + + private static final Validator TORCH_POWER_BUTTON_GESTURE_VALIDATOR = + NON_NEGATIVE_INTEGER_VALIDATOR; + /** * Whether the camera double twist gesture to flip between front and back mode should be * enabled. @@ -9360,6 +9409,7 @@ public boolean validate(@Nullable String value) { SYNC_PARENT_SOUNDS, CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED, CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, + TORCH_POWER_BUTTON_GESTURE, SYSTEM_NAVIGATION_KEYS_ENABLED, QS_TILES, DOZE_ENABLED, @@ -9530,6 +9580,8 @@ public boolean validate(@Nullable String value) { CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED_VALIDATOR); VALIDATORS.put(CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED_VALIDATOR); + VALIDATORS.put(TORCH_POWER_BUTTON_GESTURE, + TORCH_POWER_BUTTON_GESTURE_VALIDATOR); VALIDATORS.put(SYSTEM_NAVIGATION_KEYS_ENABLED, SYSTEM_NAVIGATION_KEYS_ENABLED_VALIDATOR); VALIDATORS.put(QS_TILES, QS_TILES_VALIDATOR); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index ce39d9d46c8..f7f4b05d1ae 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -644,4 +644,11 @@ interface IWindowManager * native InputManager before proceeding with tests. */ void syncInputTransactions(); + + /** + * Long screenshot + * @hide + */ + void takeOPScreenshot(int type); + void stopLongshotConnection(); } diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index e723f91887c..9c4dffa3d8b 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -138,6 +138,13 @@ public final class InputDevice implements Parcelable { */ public static final int SOURCE_CLASS_JOYSTICK = 0x00000010; + /** + * The input source is emulated by Longshot + * + * @hide + */ + public static final int SOURCE_CLASS_LONGSHOT = 0x10000000; + /** * The input source is unknown. */ diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index cdea23ab525..a5cb8f49315 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -137,6 +137,7 @@ import android.widget.ScrollBarDrawable; import com.android.internal.R; +import com.android.internal.custom.longshot.injector.ViewInjector; import com.android.internal.view.TooltipPopup; import com.android.internal.view.menu.MenuBuilder; import com.android.internal.widget.ScrollBarUtils; @@ -15301,6 +15302,7 @@ public boolean onTouchEvent(MotionEvent event) { || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; + ViewInjector.View.isInjection = event.isFromSource(InputDevice.SOURCE_CLASS_LONGSHOT); if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); @@ -15316,6 +15318,22 @@ public boolean onTouchEvent(MotionEvent event) { } } + if (action == 0 && isInScrollingContainer() && ViewInjector.View.isInjection) { + ViewParent targetView = getParent(); + while (true) { + if (targetView == null || !(targetView instanceof ViewGroup)) { + break; + } else if (((ViewGroup) targetView).shouldDelayChildPressedState()) { + int[] position = new int[2]; + ((ViewGroup) targetView).getLocationOnScreen(position); + ViewInjector.View.setScrolledViewTop(mContext, position[1]); + break; + } else { + targetView = targetView.getParent(); + } + } + } + if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: @@ -17986,7 +18004,7 @@ protected boolean awakenScrollBars(int startDelay) { protected boolean awakenScrollBars(int startDelay, boolean invalidate) { final ScrollabilityCache scrollCache = mScrollCache; - if (scrollCache == null || !scrollCache.fadeScrollBars) { + if (ViewInjector.View.onAwakenScrollBars(mContext) || scrollCache == null || !scrollCache.fadeScrollBars) { return false; } @@ -26375,6 +26393,7 @@ protected boolean overScrollBy(int deltaX, int deltaY, } onOverScrolled(newScrollX, newScrollY, clampedX, clampedY); + ViewInjector.View.onOverScrolled(mContext, clampedY); return clampedX || clampedY; } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 4798d19e946..62c8a96971b 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1110,6 +1110,12 @@ public static class LayoutParams extends ViewGroup.LayoutParams implements Parce */ public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38; + /** + * Window type: Long screenshot overlay + * @hide + */ + public static final int TYPE_SYSTEM_LONGSHOT = FIRST_SYSTEM_WINDOW + 39; + /** * End of types of system windows. */ diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 6c9fef29db6..19258934ab2 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -49,6 +49,7 @@ import android.view.inspector.InspectableProperty; import com.android.internal.R; +import com.android.internal.custom.longshot.injector.ScrollViewInjector; import java.util.List; @@ -728,6 +729,8 @@ public boolean onTouchEvent(MotionEvent ev) { } vtev.offsetLocation(0, mNestedYOffset); + ScrollViewInjector.ScrollView.isInjection = ev.isFromSource(InputDevice.SOURCE_CLASS_LONGSHOT); + switch (actionMasked) { case MotionEvent.ACTION_DOWN: { if (getChildCount() == 0) { @@ -816,12 +819,14 @@ public boolean onTouchEvent(MotionEvent ev) { if (!mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onRelease(); } + ScrollViewInjector.ScrollView.onOverScrolled(mContext, true); } else if (pulledToY > range) { mEdgeGlowBottom.onPull((float) deltaY / getHeight(), 1.f - ev.getX(activePointerIndex) / getWidth()); if (!mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onRelease(); } + ScrollViewInjector.ScrollView.onOverScrolled(mContext, true); } if (shouldDisplayEdgeEffects() && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) { diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index d037337d454..7f16ed7bbe9 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -42,6 +42,8 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; +import com.android.internal.custom.longshot.LongScreenshotManagerService; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -392,7 +394,11 @@ private static class TN extends ITransientNotification.Stub { params.width = WindowManager.LayoutParams.WRAP_CONTENT; params.format = PixelFormat.TRANSLUCENT; params.windowAnimations = com.android.internal.R.style.Animation_Toast; - params.type = WindowManager.LayoutParams.TYPE_TOAST; + if (LongScreenshotManagerService.PACKAGENAME_LONGSHOT.equals(packageName)) { + params.type = WindowManager.LayoutParams.TYPE_SYSTEM_LONGSHOT; + } else { + params.type = WindowManager.LayoutParams.TYPE_TOAST; + } params.setTitle("Toast"); params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 15b1d75e88d..2c11b0ae331 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -161,4 +161,7 @@ interface IBatteryStats { /** {@hide} */ boolean setChargingStateUpdateDelayMillis(int delay); + + /** @hide **/ + void resetStatistics(); } diff --git a/core/java/com/android/internal/custom/longshot/ILongScreenshot.aidl b/core/java/com/android/internal/custom/longshot/ILongScreenshot.aidl new file mode 100644 index 00000000000..379d0202d5a --- /dev/null +++ b/core/java/com/android/internal/custom/longshot/ILongScreenshot.aidl @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2019 The PixelExperience 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.custom.longshot; + +import com.android.internal.custom.longshot.ILongScreenshotCallback; + +/** @hide */ +interface ILongScreenshot { + + boolean isHandleState(); + + boolean isMoveState(); + + void notifyScroll(boolean isOverScroll); + + void notifyScrollViewTop(int viewTop); + + void onUnscrollableView(); + + void start(in ILongScreenshotCallback callback); + + void stopLongshot(); +} diff --git a/core/java/com/android/internal/custom/longshot/ILongScreenshotCallback.aidl b/core/java/com/android/internal/custom/longshot/ILongScreenshotCallback.aidl new file mode 100644 index 00000000000..f9060dc3b0a --- /dev/null +++ b/core/java/com/android/internal/custom/longshot/ILongScreenshotCallback.aidl @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2019 The PixelExperience 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.custom.longshot; + +/** @hide */ +interface ILongScreenshotCallback { + + void notifyMove(); + + void stop(); +} diff --git a/core/java/com/android/internal/custom/longshot/ILongScreenshotListener.aidl b/core/java/com/android/internal/custom/longshot/ILongScreenshotListener.aidl new file mode 100644 index 00000000000..0e399b3c62f --- /dev/null +++ b/core/java/com/android/internal/custom/longshot/ILongScreenshotListener.aidl @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2019 The PixelExperience 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.custom.longshot; + +/** @hide */ +interface ILongScreenshotListener { + + void onMove(); +} diff --git a/core/java/com/android/internal/custom/longshot/ILongScreenshotManager.aidl b/core/java/com/android/internal/custom/longshot/ILongScreenshotManager.aidl new file mode 100644 index 00000000000..59aacd8a3b1 --- /dev/null +++ b/core/java/com/android/internal/custom/longshot/ILongScreenshotManager.aidl @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2019 The PixelExperience 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.custom.longshot; + +import com.android.internal.custom.longshot.ILongScreenshotListener; + +/** @hide */ +interface ILongScreenshotManager { + + boolean isLongshotHandleState(); + + boolean isLongshotMode(); + + boolean isLongshotMoveState(); + + void notifyLongshotScroll(boolean isOverScroll); + + void notifyScrollViewTop(int viewTop); + + void onUnscrollableView(); + + void registerLongshotListener(in ILongScreenshotListener listener); + + void stopLongshot(); + + void takeLongshot(boolean statusBarVisible, boolean navBarVisible); + + void unregisterLongshotListener(in ILongScreenshotListener listener); +} diff --git a/core/java/com/android/internal/custom/longshot/LongScreenshotManager.java b/core/java/com/android/internal/custom/longshot/LongScreenshotManager.java new file mode 100644 index 00000000000..511346027ec --- /dev/null +++ b/core/java/com/android/internal/custom/longshot/LongScreenshotManager.java @@ -0,0 +1,125 @@ +package com.android.internal.custom.longshot; + +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +public final class LongScreenshotManager { + public static final String NAVIGATIONBAR_VISIBLE = "navigationbar_visible"; + public static final String STATUSBAR_VISIBLE = "statusbar_visible"; + private static final String TAG = "Longshot.Manager"; + private static LongScreenshotManager sInstance = null; + private final ILongScreenshotManager mService = ILongScreenshotManager.Stub.asInterface(ServiceManager.getService(Context.LONGSCREENSHOT_SERVICE)); + + private LongScreenshotManager() { + } + + public static LongScreenshotManager getInstance() { + LongScreenshotManager longScreenshotManager; + synchronized (LongScreenshotManager.class) { + if (sInstance == null || sInstance.mService == null) { + sInstance = new LongScreenshotManager(); + } + longScreenshotManager = sInstance; + } + return longScreenshotManager; + } + + public static LongScreenshotManager peekInstance() { + return sInstance; + } + + public void takeLongshot(boolean statusBarVisible, boolean navBarVisible) { + try { + if (mService != null) { + mService.takeLongshot(statusBarVisible, navBarVisible); + } + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in takeLongshot: ", e); + } + } + + public void registerLongshotListener(ILongScreenshotListener listener) { + try { + if (mService != null) { + mService.registerLongshotListener(listener); + } + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in registerLongshotListener: ", e); + } + } + + public void unregisterLongshotListener(ILongScreenshotListener listener) { + try { + if (mService != null) { + mService.unregisterLongshotListener(listener); + } + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in unregisterLongshotListener: ", e); + } + } + + public void notifyLongshotScroll(boolean isOverScroll) { + try { + if (mService != null) { + mService.notifyLongshotScroll(isOverScroll); + } + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in notifyLongshotScroll: ", e); + } + } + + public boolean isLongshotMoveState() { + try { + if (mService != null) { + return mService.isLongshotMoveState(); + } + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in isLongshotMoveState: ", e); + } + return false; + } + + public boolean isLongshotHandleState() { + try { + if (mService != null) { + return mService.isLongshotHandleState(); + } + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in isLongshotHandleState: ", e); + } + return false; + } + + public boolean isLongshotMode() { + try { + if (mService != null) { + return mService.isLongshotMode(); + } + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in isLongshotMode: ", e); + } + return false; + } + + public void notifyScrollViewTop(int viewTop) { + try { + if (mService != null) { + mService.notifyScrollViewTop(viewTop); + } + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in notifyScrollViewTop: ", e); + } + } + + public void onUnscrollableView() { + try { + if (mService != null) { + mService.onUnscrollableView(); + } + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in onUnscrollableView: ", e); + } + } +} diff --git a/core/java/com/android/internal/custom/longshot/LongScreenshotManagerService.java b/core/java/com/android/internal/custom/longshot/LongScreenshotManagerService.java new file mode 100644 index 00000000000..8b464f4801a --- /dev/null +++ b/core/java/com/android/internal/custom/longshot/LongScreenshotManagerService.java @@ -0,0 +1,195 @@ +package com.android.internal.custom.longshot; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; +import android.view.WindowManagerGlobal; +import java.util.ArrayList; +import java.util.List; + +public class LongScreenshotManagerService extends ILongScreenshotManager.Stub { + public static final String PACKAGENAME_LONGSHOT = "com.android.screenshot"; + public static final ComponentName TAKE_SCREENSHOT_COMPONENT = new ComponentName(PACKAGENAME_LONGSHOT, PACKAGENAME_LONGSHOT + ".TakeScreenshotService"); + private static final ComponentName COMPONENT_LONGSHOT = new ComponentName(PACKAGENAME_LONGSHOT, PACKAGENAME_LONGSHOT + ".LongshotService"); + private static final String TAG = "Longshot.ManagerService"; + private static LongScreenshotManagerService sInstance = null; + public Context mContext = null; + private LongshotConnection mLongshot = new LongshotConnection(); + + private class LongshotConnection extends ILongScreenshotCallback.Stub implements ServiceConnection { + private List mListeners; + public ILongScreenshot mService; + + private LongshotConnection() { + mService = null; + mListeners = new ArrayList(); + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mService = ILongScreenshot.Stub.asInterface(service); + try { + mService.start(this); + } catch (NullPointerException ignored) { + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in onServiceConnected: ", e); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + stop(); + } + + @Override + public void stop() { + mContext.unbindService(this); + mService = null; + try { + WindowManagerGlobal.getWindowManagerService().stopLongshotConnection(); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in stop: ", e); + } + } + + @Override + public void notifyMove() { + synchronized (mListeners) { + for (ILongScreenshotListener listener : mListeners) { + try { + listener.onMove(); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in notifyMove: ", e); + } + } + } + } + + public void registerListener(ILongScreenshotListener listener) { + synchronized (mListeners) { + mListeners.add(listener); + } + } + + public void unregisterListener(ILongScreenshotListener listener) { + synchronized (mListeners) { + mListeners.remove(listener); + } + } + } + + private LongScreenshotManagerService(Context context) { + mContext = context; + } + + public static LongScreenshotManagerService getInstance(Context context) { + if (sInstance == null) { + sInstance = new LongScreenshotManagerService(context); + } + return sInstance; + } + + @Override + public void takeLongshot(boolean statusBarVisible, boolean navBarVisible) { + stopLongshot(); + bindService(createLongshotIntent(statusBarVisible, navBarVisible), mLongshot, 1); + } + + @Override + public void registerLongshotListener(ILongScreenshotListener listener) { + mLongshot.registerListener(listener); + } + + @Override + public void unregisterLongshotListener(ILongScreenshotListener listener) { + mLongshot.unregisterListener(listener); + } + + @Override + public void notifyLongshotScroll(boolean isOverScroll) { + try { + mLongshot.mService.notifyScroll(isOverScroll); + } catch (NullPointerException ignored) { + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in notifyLongshotScroll: ", e); + } + } + + @Override + public boolean isLongshotMoveState() { + try { + return mLongshot.mService.isMoveState(); + } catch (NullPointerException ignored) { + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in isLongshotMoveState: ", e); + } + return false; + } + + @Override + public boolean isLongshotHandleState() { + try { + return mLongshot.mService.isHandleState(); + } catch (NullPointerException ignored) { + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in isHandleState: ", e); + } + return false; + } + + @Override + public void notifyScrollViewTop(int viewTop) { + try { + mLongshot.mService.notifyScrollViewTop(viewTop); + } catch (NullPointerException ignored) { + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in notifyScrollViewTop: ", e); + } + } + + @Override + public void onUnscrollableView() { + try { + mLongshot.mService.onUnscrollableView(); + } catch (NullPointerException ignored) { + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in onUnscrollableView: ", e); + } + } + + @Override + public boolean isLongshotMode() { + return mLongshot.mService != null; + } + + @Override + public void stopLongshot() { + if (mLongshot.mService != null) { + try { + mLongshot.mService.stopLongshot(); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in stopLongshot: ", e); + } + } + } + + private Intent createIntent(ComponentName component) { + return new Intent().setComponent(component); + } + + private Intent createLongshotIntent(boolean statusBarVisible, boolean navBarVisible) { + return createIntent(COMPONENT_LONGSHOT).putExtra(LongScreenshotManager.STATUSBAR_VISIBLE, statusBarVisible).putExtra(LongScreenshotManager.NAVIGATIONBAR_VISIBLE, navBarVisible); + } + + private boolean bindService(Intent service, ServiceConnection conn, int flags) { + if (service != null && conn != null) { + return mContext.bindServiceAsUser(service, conn, flags, UserHandle.CURRENT); + } + return false; + } +} diff --git a/core/java/com/android/internal/custom/longshot/LongScreenshotService.java b/core/java/com/android/internal/custom/longshot/LongScreenshotService.java new file mode 100644 index 00000000000..fe68f720d45 --- /dev/null +++ b/core/java/com/android/internal/custom/longshot/LongScreenshotService.java @@ -0,0 +1,38 @@ +package com.android.internal.custom.longshot; + +import android.content.Context; + +public abstract class LongScreenshotService extends ILongScreenshot.Stub { + private static final String TAG = "Longshot.Service"; + protected Context mContext = null; + protected boolean mNavBarVisible = false; + protected boolean mStatusBarVisible = false; + + public LongScreenshotService(Context context, boolean statusBarVisible, boolean navBarVisible) { + mContext = context; + mStatusBarVisible = statusBarVisible; + mNavBarVisible = navBarVisible; + } + + @Override + public void start(ILongScreenshotCallback callback) { + } + + @Override + public void notifyScroll(boolean isOverScroll) { + } + + @Override + public boolean isMoveState() { + return false; + } + + @Override + public boolean isHandleState() { + return false; + } + + @Override + public void stopLongshot() { + } +} diff --git a/core/java/com/android/internal/custom/longshot/injector/ScrollViewInjector.java b/core/java/com/android/internal/custom/longshot/injector/ScrollViewInjector.java new file mode 100644 index 00000000000..dea5cf66b51 --- /dev/null +++ b/core/java/com/android/internal/custom/longshot/injector/ScrollViewInjector.java @@ -0,0 +1,22 @@ +package com.android.internal.custom.longshot.injector; + +import android.content.Context; + +import com.android.internal.custom.longshot.LongScreenshotManager; + +public class ScrollViewInjector { + + public static class ScrollView { + private static final String TAG = "ScrollViewInjector"; + public static boolean isInjection = false; + + public static void onOverScrolled(Context context, boolean isOverScroll) { + if (isInjection) { + LongScreenshotManager sm = (LongScreenshotManager) context.getSystemService(Context.LONGSCREENSHOT_SERVICE); + if (sm != null && sm.isLongshotMoveState()) { + sm.notifyLongshotScroll(isOverScroll); + } + } + } + } +} diff --git a/core/java/com/android/internal/custom/longshot/injector/ViewInjector.java b/core/java/com/android/internal/custom/longshot/injector/ViewInjector.java new file mode 100644 index 00000000000..cf3a7e657cc --- /dev/null +++ b/core/java/com/android/internal/custom/longshot/injector/ViewInjector.java @@ -0,0 +1,181 @@ +package com.android.internal.custom.longshot.injector; + +import android.content.Context; + +import com.android.internal.custom.longshot.LongScreenshotManager; + +import java.util.ArrayList; +import java.util.List; + +public class ViewInjector { + + public static class View { + + private static final List ELEMENTS_NOOVERSCROLL = new ArrayList(); + private static final List ELEMENTS_NOSCROLL = new ArrayList(); + private static final List ELEMENTS_OVERSCROLL = new ArrayList(); + private static final List ELEMENTS_SCROLL = new ArrayList(); + private static final String TAG = "ViewInjector"; + public static boolean isInjection = false; + + private enum Element { + SCROLL(5, "AbsListView.trackMotionScroll"), + QQSCROLL(7, "tencent.widget.AbsListView.onTouchEvent"), + MMAWAKEN12(12, "tencent.mm.ui.base.MMPullDownView.dispatchTouchEvent"), + MMAWAKEN14(14, "tencent.mm.ui.base.MMPullDownView.dispatchTouchEvent"), + MMAWAKEN15(15, "tencent.mm.ui.base.MMPullDownView.dispatchTouchEvent"), + OVERSCROLL(5, "AbsListView.onOverScrolled"), + CONTENTSCROLL(4, "ContentView.onScrollChanged"), + MMCHANGE9(9, "tencent.mm.ui.base.MMPullDownView.dispatchTouchEvent"), + MMCHANGE12(12, "tencent.mm.ui.base.MMPullDownView.dispatchTouchEvent"), + MMCHANGE14(14, "tencent.mm.ui.base.MMPullDownView.dispatchTouchEvent"), + MMCHANGE15(15, "tencent.mm.ui.base.MMPullDownView.dispatchTouchEvent"), + BROWSERSCROLL(14, "oppo.browser.navigation.widget.NavigationView.dispatchTouchEvent"), + QZONESCROLL(8, "qzone.widget.QZonePullToRefreshListView.onScrollChanged"), + WEBSCROLL(16, "WebView$PrivateAccess.overScrollBy"), + LISTOVERSCROLL(6, "AbsListView.onTouchEvent"), + WEBOVERSCROLL(5, "WebView$PrivateAccess.overScrollBy"), + BROWSEROVERSCROLL(11, "oppo.browser.navigation.widget.NavigationView.dispatchTouchEvent"); + + private String mName; + private int mPosition; + + private Element(int position, String name) { + this.mName = null; + this.mPosition = -1; + this.mPosition = position; + this.mName = name; + } + + public int getPosition() { + return this.mPosition; + } + + public String getName() { + return this.mName; + } + + public String getNameString() { + StringBuilder sb = new StringBuilder(); + sb.append("."); + sb.append(getName()); + sb.append("("); + return sb.toString(); + } + } + + public static void onUnscrollableView(Context context) { + if (isInjection) { + LongScreenshotManager sm = (LongScreenshotManager) context.getSystemService(Context.LONGSCREENSHOT_SERVICE); + if (sm != null) { + sm.onUnscrollableView(); + } + } + } + + public static void setScrolledViewTop(Context context, int top) { + if (isInjection) { + LongScreenshotManager sm = (LongScreenshotManager) context.getSystemService(Context.LONGSCREENSHOT_SERVICE); + if (sm != null) { + sm.notifyScrollViewTop(top); + } + } + } + + public static void onOverScrolled(Context context, boolean isOverScroll) { + if (isInjection) { + LongScreenshotManager sm = (LongScreenshotManager) context.getSystemService(Context.LONGSCREENSHOT_SERVICE); + if (sm != null && sm.isLongshotMoveState()) { + StackTraceElement[] stacks = Thread.currentThread().getStackTrace(); + ELEMENTS_OVERSCROLL.add(Element.LISTOVERSCROLL); + ELEMENTS_NOOVERSCROLL.add(Element.WEBOVERSCROLL); + ELEMENTS_NOOVERSCROLL.add(Element.BROWSERSCROLL); + ELEMENTS_NOOVERSCROLL.add(Element.BROWSEROVERSCROLL); + if (!isElement(stacks, ELEMENTS_NOOVERSCROLL)) { + if (isElement(stacks, ELEMENTS_OVERSCROLL)) { + sm.notifyLongshotScroll(true); + } else { + sm.notifyLongshotScroll(false); + } + } + clearElements(); + } + } + } + + public static void onScrollChanged(Context context, boolean canScrollVertically) { + if (isInjection) { + LongScreenshotManager sm = (LongScreenshotManager) context.getSystemService(Context.LONGSCREENSHOT_SERVICE); + if (sm != null && sm.isLongshotMoveState()) { + StackTraceElement[] stacks = Thread.currentThread().getStackTrace(); + ELEMENTS_NOSCROLL.add(Element.MMCHANGE9); + ELEMENTS_NOSCROLL.add(Element.MMCHANGE12); + ELEMENTS_NOSCROLL.add(Element.MMCHANGE14); + ELEMENTS_NOSCROLL.add(Element.MMCHANGE15); + ELEMENTS_NOSCROLL.add(Element.CONTENTSCROLL); + ELEMENTS_NOSCROLL.add(Element.BROWSERSCROLL); + ELEMENTS_NOSCROLL.add(Element.QZONESCROLL); + ELEMENTS_NOSCROLL.add(Element.WEBSCROLL); + if (!isElement(stacks, ELEMENTS_NOSCROLL)) { + if (!canScrollVertically) { + sm.notifyLongshotScroll(true); + } else { + sm.notifyLongshotScroll(false); + } + } + clearElements(); + } + } + } + + public static boolean onAwakenScrollBars(Context context) { + if (!isInjection) { + return false; + } + boolean result = false; + LongScreenshotManager sm = (LongScreenshotManager) context.getSystemService(Context.LONGSCREENSHOT_SERVICE); + if (sm != null) { + result = sm.isLongshotMoveState(); + if (result) { + StackTraceElement[] stacks = Thread.currentThread().getStackTrace(); + ELEMENTS_OVERSCROLL.add(Element.OVERSCROLL); + ELEMENTS_NOSCROLL.add(Element.MMAWAKEN12); + ELEMENTS_NOSCROLL.add(Element.MMAWAKEN14); + ELEMENTS_NOSCROLL.add(Element.MMAWAKEN15); + ELEMENTS_SCROLL.add(Element.QQSCROLL); + ELEMENTS_SCROLL.add(Element.SCROLL); + if (!isElement(stacks, ELEMENTS_NOSCROLL)) { + if (isElement(stacks, ELEMENTS_OVERSCROLL)) { + sm.notifyLongshotScroll(true); + } else if (isElement(stacks, ELEMENTS_SCROLL)) { + sm.notifyLongshotScroll(false); + } + } + clearElements(); + } + } + return result; + } + + private static boolean isElement(StackTraceElement[] stacks, List elements) { + boolean result = false; + for (Element element : elements) { + int pos = element.getPosition(); + if (stacks.length > pos) { + result = stacks[pos].toString().contains(element.getNameString()); + if (result) { + break; + } + } + } + return result; + } + + private static void clearElements() { + ELEMENTS_SCROLL.clear(); + ELEMENTS_NOSCROLL.clear(); + ELEMENTS_OVERSCROLL.clear(); + ELEMENTS_NOOVERSCROLL.clear(); + } + } +} diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java index e85508ed789..6575979a1c4 100644 --- a/core/java/com/android/internal/os/BatteryStatsHelper.java +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -263,6 +263,16 @@ public void clearStats() { mStats = null; } + private void clearAllStats() { + clearStats(); + sStatsXfer = null; + sBatteryBroadcastXfer = null; + for (File f : sFileXfer.keySet()) { + f.delete(); + } + sFileXfer.clear(); + } + @UnsupportedAppUsage public BatteryStats getStats() { if (mStats == null) { @@ -1053,6 +1063,15 @@ private void load() { } } + public void resetStatistics() { + try { + clearAllStats(); + mBatteryInfo.resetStatistics(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException:", e); + } + } + private static BatteryStatsImpl getStats(IBatteryStats service) { try { ParcelFileDescriptor pfd = service.getStatisticsStream(); diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 482e5961c1e..84ed49bbea2 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -155,7 +155,7 @@ oneway interface IStatusBar void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId); // Used to hide the dialog when a biometric is authenticated - void onBiometricAuthenticated(boolean authenticated, String failureReason); + void onBiometricAuthenticated(boolean authenticated, String failureReason, boolean requireConfirmation); // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc void onBiometricHelp(String message); // Used to set a message - the dialog will dismiss after a certain amount of time diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 5921f64abed..cf97da1d221 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -103,7 +103,7 @@ interface IStatusBarService void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId); // Used to hide the dialog when a biometric is authenticated - void onBiometricAuthenticated(boolean authenticated, String failureReason); + void onBiometricAuthenticated(boolean authenticated, String failureReason, boolean requireConfirmation); // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc void onBiometricHelp(String message); // Used to set a message - the dialog will dismiss after a certain amount of time diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java index 68f32e5f4b0..7c2c8afd9cb 100644 --- a/core/java/com/android/internal/util/ScreenshotHelper.java +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -9,6 +9,7 @@ import android.content.Intent; import android.content.ServiceConnection; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -16,6 +17,9 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; +import android.view.WindowManager; + +import com.android.internal.custom.longshot.LongScreenshotManagerService; import java.util.function.Consumer; @@ -27,6 +31,8 @@ public class ScreenshotHelper { "com.android.systemui.screenshot.TakeScreenshotService"; private static final String SYSUI_SCREENSHOT_ERROR_RECEIVER = "com.android.systemui.screenshot.ScreenshotServiceErrorReceiver"; + private static final String SYSUI_SCREENSHOT_CAPTURE_ERROR_RECEIVER = + "com.android.systemui.screenshot.ScreenshotServiceCaptureErrorReceiver"; // Time until we give up on the screenshot & show an error instead. private final int SCREENSHOT_TIMEOUT_MS = 10000; @@ -35,6 +41,19 @@ public class ScreenshotHelper { private ServiceConnection mScreenshotConnection = null; private final Context mContext; + private Handler mHandler; + final Runnable mLongshotTimeout = new Runnable() { + public void run() { + synchronized (mScreenshotLock) { + if (mScreenshotConnection != null) { + mContext.unbindService(mScreenshotConnection); + mScreenshotConnection = null; + notifyScreenshotError(); + } + } + } + }; + public ScreenshotHelper(Context context) { mContext = context; } @@ -197,4 +216,106 @@ private void notifyScreenshotError() { mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT); } + /** + * Long screenshot methods + */ + public void takeScreenshot(final int screenshotType, final boolean hasStatus, + final boolean hasNav, @NonNull Handler handler, + final boolean isLongshot, final Bundle screenshotBundle) { + if (screenshotType != WindowManager.TAKE_SCREENSHOT_FULLSCREEN){ + takeScreenshot(screenshotType, hasStatus, hasNav, SCREENSHOT_TIMEOUT_MS, handler, + null); + return; + } + synchronized (mScreenshotLock) { + if (mScreenshotConnection != null) { + return; + } + mHandler = handler; + final Intent serviceIntent = new Intent(); + serviceIntent.setComponent(LongScreenshotManagerService.TAKE_SCREENSHOT_COMPONENT); + ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mScreenshotLock) { + if (mScreenshotConnection != this) { + return; + } + Messenger messenger = new Messenger(service); + Message msg = Message.obtain(null, screenshotType); + final ServiceConnection myConn = this; + Handler h = new Handler(handler.getLooper()) { + @Override + public void handleMessage(Message msg) { + synchronized (mScreenshotLock) { + if (msg.what == 2) { + handler.removeCallbacks(mLongshotTimeout); + } else if (mScreenshotConnection == myConn) { + mContext.unbindService(mScreenshotConnection); + mScreenshotConnection = null; + handler.removeCallbacks(mLongshotTimeout); + } + } + } + }; + msg.replyTo = new Messenger(h); + msg.arg1 = hasStatus ? 1: 0; + msg.arg2 = hasNav ? 1: 0; + msg.obj = screenshotBundle; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "Couldn't take screenshot: " + e); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + synchronized (mScreenshotLock) { + if (mScreenshotConnection != null) { + mContext.unbindService(mScreenshotConnection); + mScreenshotConnection = null; + handler.removeCallbacks(mLongshotTimeout); + notifyScreenshotError(); + } + } + } + }; + if (mContext.bindServiceAsUser(serviceIntent, conn, + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, + UserHandle.CURRENT)) { + mScreenshotConnection = conn; + if (isLongshot) { + handler.postDelayed(mLongshotTimeout, 120000); + } else { + handler.postDelayed(mLongshotTimeout, SCREENSHOT_TIMEOUT_MS); + } + } + } + } + + public void stopLongshotConnection() { + synchronized (mScreenshotLock) { + if (mScreenshotConnection != null) { + mContext.unbindService(mScreenshotConnection); + mScreenshotConnection = null; + mHandler.removeCallbacks(mLongshotTimeout); + } + } + } + + public void notifyScreenshotCaptureError() { + // If the service process is killed, then ask it to clean up after itself + final ComponentName errorComponent = new ComponentName(SYSUI_PACKAGE, + SYSUI_SCREENSHOT_CAPTURE_ERROR_RECEIVER); + // Broadcast needs to have a valid action. We'll just pick + // a generic one, since the receiver here doesn't care. + Intent errorIntent = new Intent(Intent.ACTION_USER_PRESENT); + errorIntent.setComponent(errorComponent); + errorIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | + Intent.FLAG_RECEIVER_FOREGROUND); + mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT); + } + } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index c29e823218e..4414c0f07e9 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -71,4 +71,7 @@ interface IInputMethodManager { void reportActivityView(in IInputMethodClient parentClient, int childDisplayId, in float[] matrixValues); + + // Long screenshot + boolean hideSoftInputForLongshot(int flags, in ResultReceiver resultReceiver); } diff --git a/core/res/res/values/reloaded_config.xml b/core/res/res/values/reloaded_config.xml index 0a2ac21e6fc..ae8f931505f 100644 --- a/core/res/res/values/reloaded_config.xml +++ b/core/res/res/values/reloaded_config.xml @@ -47,4 +47,10 @@ true + + 80 + 60 + /sys/class/power_supply/battery/charging_enabled + 0 + 1 diff --git a/core/res/res/values/reloaded_symbols.xml b/core/res/res/values/reloaded_symbols.xml index 56c0713838d..62831e51c7d 100644 --- a/core/res/res/values/reloaded_symbols.xml +++ b/core/res/res/values/reloaded_symbols.xml @@ -48,4 +48,10 @@ + + + + + + diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml index 3e53a383854..4172f69e516 100644 --- a/data/etc/com.android.settings.xml +++ b/data/etc/com.android.settings.xml @@ -20,6 +20,7 @@ + diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 918f6cbfc6a..ab839efe1a4 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -327,6 +327,11 @@ applications that come with the platform + + + + + diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java index 0a37cc6cb6c..678a0adf218 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java @@ -35,7 +35,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; -import android.os.storage.StorageVolume; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -64,29 +63,18 @@ public class UninstallAlertDialogFragment extends DialogFragment implements * @return The number of bytes. */ private long getAppDataSizeForUser(@NonNull String pkg, @NonNull UserHandle user) { - StorageManager storageManager = getContext().getSystemService(StorageManager.class); + PackageManager packageManager = getContext().getPackageManager(); StorageStatsManager storageStatsManager = getContext().getSystemService(StorageStatsManager.class); - List volumes = storageManager.getStorageVolumes(); - long appDataSize = 0; - - int numVolumes = volumes.size(); - for (int i = 0; i < numVolumes; i++) { - StorageStats stats; - try { - stats = storageStatsManager.queryStatsForPackage(convert(volumes.get(i).getUuid()), - pkg, user); - } catch (PackageManager.NameNotFoundException | IOException e) { - Log.e(LOG_TAG, "Cannot determine amount of app data for " + pkg + " on " - + volumes.get(i) + " (user " + user + ")", e); - continue; - } - - appDataSize += stats.getDataBytes(); + try { + ApplicationInfo info = packageManager.getApplicationInfo(pkg, 0); + return storageStatsManager.queryStatsForPackage( + info.storageUuid, pkg, user).getDataBytes(); + } catch (PackageManager.NameNotFoundException | IOException e) { + Log.e(LOG_TAG, "Cannot determine amount of app data for " + pkg, e); + return 0; } - - return appDataSize; } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 9c447983b67..00aedfbcaa1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -28,6 +28,7 @@ import android.telephony.ServiceState; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.custom.longshot.LongScreenshotManagerService; import com.android.internal.util.UserIcons; import com.android.settingslib.drawable.UserIconDrawable; @@ -318,6 +319,7 @@ public static boolean isSystemPackage(Resources resources, PackageManager pm, Pa || pkg.packageName.equals(sServicesSystemSharedLibPackageName) || pkg.packageName.equals(sSharedSystemSharedLibPackageName) || pkg.packageName.equals(PrintManager.PRINT_SPOOLER_PACKAGE_NAME) + || pkg.packageName.equals(LongScreenshotManagerService.PACKAGENAME_LONGSHOT) || isDeviceProvisioningPackage(resources, pkg.packageName); } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index c2bc7297279..988c024d4bd 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -258,6 +258,16 @@ + + + + + + + + + + + + + + + + @@ -661,7 +679,12 @@ android:resource="@xml/fileprovider" /> - + + + diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index 22b0ab7dde4..563a9325039 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -35,3 +35,6 @@ *; } -keep class androidx.core.app.CoreComponentFactory + +# Custom rules +-keep class com.google.android.systemui.** { *; } diff --git a/packages/SystemUI/res/drawable/ic_qs_reboot.xml b/packages/SystemUI/res/drawable/ic_qs_reboot.xml new file mode 100644 index 00000000000..7d003b48192 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_reboot.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/packages/SystemUI/res/drawable/ic_qs_reboot_recovery.xml b/packages/SystemUI/res/drawable/ic_qs_reboot_recovery.xml new file mode 100644 index 00000000000..29328460a19 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_reboot_recovery.xml @@ -0,0 +1,54 @@ + + + + + + + + + diff --git a/packages/SystemUI/res/layout/biometric_dialog.xml b/packages/SystemUI/res/layout/biometric_dialog.xml index c560d7e8f12..d3149f7a944 100644 --- a/packages/SystemUI/res/layout/biometric_dialog.xml +++ b/packages/SystemUI/res/layout/biometric_dialog.xml @@ -106,7 +106,7 @@ android:layout_width="@dimen/biometric_dialog_biometric_icon_size" android:layout_height="@dimen/biometric_dialog_biometric_icon_size" android:layout_gravity="center_horizontal" - android:layout_marginTop="48dp" + android:layout_marginTop="24dp" android:scaleType="fitXY" /> + + + + %d uur + %d uur + + + %d min. + %d min. + + diff --git a/packages/SystemUI/res/values-af-rZA/smartspace_strings.xml b/packages/SystemUI/res/values-af-rZA/smartspace_strings.xml new file mode 100644 index 00000000000..25f5785c586 --- /dev/null +++ b/packages/SystemUI/res/values-af-rZA/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Nou + diff --git a/packages/SystemUI/res/values-am-rET/smartspace_plurals.xml b/packages/SystemUI/res/values-am-rET/smartspace_plurals.xml new file mode 100644 index 00000000000..3d297c0214e --- /dev/null +++ b/packages/SystemUI/res/values-am-rET/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d ሰዓ + %d ሰዓ + + + %d ደቂቃ + %d ደቂቃ + + diff --git a/packages/SystemUI/res/values-am-rET/smartspace_strings.xml b/packages/SystemUI/res/values-am-rET/smartspace_strings.xml new file mode 100644 index 00000000000..82120c8b1d5 --- /dev/null +++ b/packages/SystemUI/res/values-am-rET/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + አሁን + diff --git a/packages/SystemUI/res/values-ar-rSA/smartspace_plurals.xml b/packages/SystemUI/res/values-ar-rSA/smartspace_plurals.xml new file mode 100644 index 00000000000..165f6ce8789 --- /dev/null +++ b/packages/SystemUI/res/values-ar-rSA/smartspace_plurals.xml @@ -0,0 +1,20 @@ + + + + + ‏%d ساعة + ‏ساعة واحدة (%d) + ‏ساعتان (%d) + ‏%d ساعات + ‏%d ساعة + ‏%d ساعة + + + ‏%d دقيقة + ‏دقيقة واحدة (%d) + ‏دقيقتان (%d) + ‏%d دقائق + ‏%d دقيقة + ‏%d دقيقة + + diff --git a/packages/SystemUI/res/values-ar-rSA/smartspace_strings.xml b/packages/SystemUI/res/values-ar-rSA/smartspace_strings.xml new file mode 100644 index 00000000000..65350654b86 --- /dev/null +++ b/packages/SystemUI/res/values-ar-rSA/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + الآن + diff --git a/packages/SystemUI/res/values-as-rIN/smartspace_plurals.xml b/packages/SystemUI/res/values-as-rIN/smartspace_plurals.xml new file mode 100644 index 00000000000..78bdd526810 --- /dev/null +++ b/packages/SystemUI/res/values-as-rIN/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d ঘ. + %d ঘ. + + + %d মি. + %d মি. + + diff --git a/packages/SystemUI/res/values-as-rIN/smartspace_strings.xml b/packages/SystemUI/res/values-as-rIN/smartspace_strings.xml new file mode 100644 index 00000000000..61d50b96cd3 --- /dev/null +++ b/packages/SystemUI/res/values-as-rIN/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + এতিয়া + diff --git a/packages/SystemUI/res/values-az-rAZ/smartspace_plurals.xml b/packages/SystemUI/res/values-az-rAZ/smartspace_plurals.xml new file mode 100644 index 00000000000..0ed05017b9e --- /dev/null +++ b/packages/SystemUI/res/values-az-rAZ/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d saat + %d saat + + + %d dəq + %d dəq + + diff --git a/packages/SystemUI/res/values-az-rAZ/smartspace_strings.xml b/packages/SystemUI/res/values-az-rAZ/smartspace_strings.xml new file mode 100644 index 00000000000..79395b9e66d --- /dev/null +++ b/packages/SystemUI/res/values-az-rAZ/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + İndi + diff --git a/packages/SystemUI/res/values-be-rBY/smartspace_plurals.xml b/packages/SystemUI/res/values-be-rBY/smartspace_plurals.xml new file mode 100644 index 00000000000..eb76c8b4c01 --- /dev/null +++ b/packages/SystemUI/res/values-be-rBY/smartspace_plurals.xml @@ -0,0 +1,16 @@ + + + + + %d гадз + %d гадз + %d гадз + %d гадз + + + %d хв + %d хв + %d хв + %d хв + + diff --git a/packages/SystemUI/res/values-be-rBY/smartspace_strings.xml b/packages/SystemUI/res/values-be-rBY/smartspace_strings.xml new file mode 100644 index 00000000000..3a0e098c1fc --- /dev/null +++ b/packages/SystemUI/res/values-be-rBY/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Зараз + diff --git a/packages/SystemUI/res/values-bg-rBG/smartspace_plurals.xml b/packages/SystemUI/res/values-bg-rBG/smartspace_plurals.xml new file mode 100644 index 00000000000..6529dc5fa79 --- /dev/null +++ b/packages/SystemUI/res/values-bg-rBG/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d ч + %d ч + + + %d мин + %d мин + + diff --git a/packages/SystemUI/res/values-bg-rBG/smartspace_strings.xml b/packages/SystemUI/res/values-bg-rBG/smartspace_strings.xml new file mode 100644 index 00000000000..ef518dfed15 --- /dev/null +++ b/packages/SystemUI/res/values-bg-rBG/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s и %2$s + Сега + diff --git a/packages/SystemUI/res/values-bn-rBD/smartspace_plurals.xml b/packages/SystemUI/res/values-bn-rBD/smartspace_plurals.xml new file mode 100644 index 00000000000..2cf1892dcd4 --- /dev/null +++ b/packages/SystemUI/res/values-bn-rBD/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d ঘণ্টা + %d ঘণ্টা + + + %d মিনিট + %d মিনিট + + diff --git a/packages/SystemUI/res/values-bn-rBD/smartspace_strings.xml b/packages/SystemUI/res/values-bn-rBD/smartspace_strings.xml new file mode 100644 index 00000000000..e8f8515062b --- /dev/null +++ b/packages/SystemUI/res/values-bn-rBD/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + এখন + diff --git a/packages/SystemUI/res/values-bs-rBA/smartspace_plurals.xml b/packages/SystemUI/res/values-bs-rBA/smartspace_plurals.xml new file mode 100644 index 00000000000..3bb976fa885 --- /dev/null +++ b/packages/SystemUI/res/values-bs-rBA/smartspace_plurals.xml @@ -0,0 +1,14 @@ + + + + + %d h + %d h + %d h + + + %d min + %d min + %d min + + diff --git a/packages/SystemUI/res/values-bs-rBA/smartspace_strings.xml b/packages/SystemUI/res/values-bs-rBA/smartspace_strings.xml new file mode 100644 index 00000000000..cc48f5355d4 --- /dev/null +++ b/packages/SystemUI/res/values-bs-rBA/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Sada + diff --git a/packages/SystemUI/res/values-ca-rES/smartspace_plurals.xml b/packages/SystemUI/res/values-ca-rES/smartspace_plurals.xml new file mode 100644 index 00000000000..8a97f3f31fe --- /dev/null +++ b/packages/SystemUI/res/values-ca-rES/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d h + %d h + + + %d min + %d min + + diff --git a/packages/SystemUI/res/values-ca-rES/smartspace_strings.xml b/packages/SystemUI/res/values-ca-rES/smartspace_strings.xml new file mode 100644 index 00000000000..2c41ab00fa6 --- /dev/null +++ b/packages/SystemUI/res/values-ca-rES/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Ara + diff --git a/packages/SystemUI/res/values-cs-rCZ/smartspace_plurals.xml b/packages/SystemUI/res/values-cs-rCZ/smartspace_plurals.xml new file mode 100644 index 00000000000..6723aee869c --- /dev/null +++ b/packages/SystemUI/res/values-cs-rCZ/smartspace_plurals.xml @@ -0,0 +1,16 @@ + + + + + %d h + %d h + %d h + %d h + + + %d min + %d min + %d min + %d min + + diff --git a/packages/SystemUI/res/values-cs-rCZ/smartspace_strings.xml b/packages/SystemUI/res/values-cs-rCZ/smartspace_strings.xml new file mode 100644 index 00000000000..39dcb42e93d --- /dev/null +++ b/packages/SystemUI/res/values-cs-rCZ/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Právě teď + diff --git a/packages/SystemUI/res/values-da-rDK/smartspace_plurals.xml b/packages/SystemUI/res/values-da-rDK/smartspace_plurals.xml new file mode 100644 index 00000000000..58718145b74 --- /dev/null +++ b/packages/SystemUI/res/values-da-rDK/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d t. + %d t. + + + %d min. + %d min. + + diff --git a/packages/SystemUI/res/values-da-rDK/smartspace_strings.xml b/packages/SystemUI/res/values-da-rDK/smartspace_strings.xml new file mode 100644 index 00000000000..2a0f8431c10 --- /dev/null +++ b/packages/SystemUI/res/values-da-rDK/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s og %2$s + Nu + diff --git a/packages/SystemUI/res/values-de-rDE/smartspace_plurals.xml b/packages/SystemUI/res/values-de-rDE/smartspace_plurals.xml new file mode 100644 index 00000000000..8a97f3f31fe --- /dev/null +++ b/packages/SystemUI/res/values-de-rDE/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d h + %d h + + + %d min + %d min + + diff --git a/packages/SystemUI/res/values-de-rDE/smartspace_strings.xml b/packages/SystemUI/res/values-de-rDE/smartspace_strings.xml new file mode 100644 index 00000000000..1b6b992f207 --- /dev/null +++ b/packages/SystemUI/res/values-de-rDE/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Jetzt + diff --git a/packages/SystemUI/res/values-el-rGR/smartspace_plurals.xml b/packages/SystemUI/res/values-el-rGR/smartspace_plurals.xml new file mode 100644 index 00000000000..146fa950706 --- /dev/null +++ b/packages/SystemUI/res/values-el-rGR/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d ώ. + %d ώ. + + + %d λ. + %d λ. + + diff --git a/packages/SystemUI/res/values-el-rGR/smartspace_strings.xml b/packages/SystemUI/res/values-el-rGR/smartspace_strings.xml new file mode 100644 index 00000000000..187278da01d --- /dev/null +++ b/packages/SystemUI/res/values-el-rGR/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Τώρα + diff --git a/packages/SystemUI/res/values-es-rES/smartspace_plurals.xml b/packages/SystemUI/res/values-es-rES/smartspace_plurals.xml new file mode 100644 index 00000000000..8a97f3f31fe --- /dev/null +++ b/packages/SystemUI/res/values-es-rES/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d h + %d h + + + %d min + %d min + + diff --git a/packages/SystemUI/res/values-es-rES/smartspace_strings.xml b/packages/SystemUI/res/values-es-rES/smartspace_strings.xml new file mode 100644 index 00000000000..a7041fecc63 --- /dev/null +++ b/packages/SystemUI/res/values-es-rES/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s y %2$s + Ahora + diff --git a/packages/SystemUI/res/values-es-rMX/smartspace_plurals.xml b/packages/SystemUI/res/values-es-rMX/smartspace_plurals.xml new file mode 100644 index 00000000000..8a97f3f31fe --- /dev/null +++ b/packages/SystemUI/res/values-es-rMX/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d h + %d h + + + %d min + %d min + + diff --git a/packages/SystemUI/res/values-es-rMX/smartspace_strings.xml b/packages/SystemUI/res/values-es-rMX/smartspace_strings.xml new file mode 100644 index 00000000000..a7041fecc63 --- /dev/null +++ b/packages/SystemUI/res/values-es-rMX/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s y %2$s + Ahora + diff --git a/packages/SystemUI/res/values-et-rEE/smartspace_plurals.xml b/packages/SystemUI/res/values-et-rEE/smartspace_plurals.xml new file mode 100644 index 00000000000..8597a44c759 --- /dev/null +++ b/packages/SystemUI/res/values-et-rEE/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d h + %d h + + + %d min + %d min + + diff --git a/packages/SystemUI/res/values-et-rEE/smartspace_strings.xml b/packages/SystemUI/res/values-et-rEE/smartspace_strings.xml new file mode 100644 index 00000000000..45c6dee7ac9 --- /dev/null +++ b/packages/SystemUI/res/values-et-rEE/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Kohe + diff --git a/packages/SystemUI/res/values-eu-rES/smartspace_plurals.xml b/packages/SystemUI/res/values-eu-rES/smartspace_plurals.xml new file mode 100644 index 00000000000..8a97f3f31fe --- /dev/null +++ b/packages/SystemUI/res/values-eu-rES/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d h + %d h + + + %d min + %d min + + diff --git a/packages/SystemUI/res/values-eu-rES/smartspace_strings.xml b/packages/SystemUI/res/values-eu-rES/smartspace_strings.xml new file mode 100644 index 00000000000..1578792ff0a --- /dev/null +++ b/packages/SystemUI/res/values-eu-rES/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s eta %2$s + Orain + diff --git a/packages/SystemUI/res/values-fa-rIR/smartspace_plurals.xml b/packages/SystemUI/res/values-fa-rIR/smartspace_plurals.xml new file mode 100644 index 00000000000..c27322c8aa1 --- /dev/null +++ b/packages/SystemUI/res/values-fa-rIR/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + ‏%d ساعت + ‏%d ساعت + + + ‏%d دقیقه + ‏%d دقیقه + + diff --git a/packages/SystemUI/res/values-fa-rIR/smartspace_strings.xml b/packages/SystemUI/res/values-fa-rIR/smartspace_strings.xml new file mode 100644 index 00000000000..03db327fd7a --- /dev/null +++ b/packages/SystemUI/res/values-fa-rIR/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + ابھی + diff --git a/packages/SystemUI/res/values-fi-rFI/smartspace_plurals.xml b/packages/SystemUI/res/values-fi-rFI/smartspace_plurals.xml new file mode 100644 index 00000000000..8597a44c759 --- /dev/null +++ b/packages/SystemUI/res/values-fi-rFI/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d h + %d h + + + %d min + %d min + + diff --git a/packages/SystemUI/res/values-fi-rFI/smartspace_strings.xml b/packages/SystemUI/res/values-fi-rFI/smartspace_strings.xml new file mode 100644 index 00000000000..3908f4baa1f --- /dev/null +++ b/packages/SystemUI/res/values-fi-rFI/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Nyt + diff --git a/packages/SystemUI/res/values-fr-rFR/smartspace_plurals.xml b/packages/SystemUI/res/values-fr-rFR/smartspace_plurals.xml new file mode 100644 index 00000000000..8a97f3f31fe --- /dev/null +++ b/packages/SystemUI/res/values-fr-rFR/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d h + %d h + + + %d min + %d min + + diff --git a/packages/SystemUI/res/values-fr-rFR/smartspace_strings.xml b/packages/SystemUI/res/values-fr-rFR/smartspace_strings.xml new file mode 100644 index 00000000000..cb40ae98bd1 --- /dev/null +++ b/packages/SystemUI/res/values-fr-rFR/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + À l\'instant + diff --git a/packages/SystemUI/res/values-gl-rES/smartspace_plurals.xml b/packages/SystemUI/res/values-gl-rES/smartspace_plurals.xml new file mode 100644 index 00000000000..8597a44c759 --- /dev/null +++ b/packages/SystemUI/res/values-gl-rES/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d h + %d h + + + %d min + %d min + + diff --git a/packages/SystemUI/res/values-gl-rES/smartspace_strings.xml b/packages/SystemUI/res/values-gl-rES/smartspace_strings.xml new file mode 100644 index 00000000000..2238499995e --- /dev/null +++ b/packages/SystemUI/res/values-gl-rES/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Agora + diff --git a/packages/SystemUI/res/values-gu-rIN/smartspace_plurals.xml b/packages/SystemUI/res/values-gu-rIN/smartspace_plurals.xml new file mode 100644 index 00000000000..25491ea0b1c --- /dev/null +++ b/packages/SystemUI/res/values-gu-rIN/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d કલાક + %d કલાક + + + %d મિનિટ + %d મિનિટ + + diff --git a/packages/SystemUI/res/values-gu-rIN/smartspace_strings.xml b/packages/SystemUI/res/values-gu-rIN/smartspace_strings.xml new file mode 100644 index 00000000000..9865237d4a1 --- /dev/null +++ b/packages/SystemUI/res/values-gu-rIN/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + હમણાં + diff --git a/packages/SystemUI/res/values-hi-rIN/smartspace_plurals.xml b/packages/SystemUI/res/values-hi-rIN/smartspace_plurals.xml new file mode 100644 index 00000000000..5e5d6efcc54 --- /dev/null +++ b/packages/SystemUI/res/values-hi-rIN/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d घंटे + %d घंटे + + + %d मिनट + %d मिनट + + diff --git a/packages/SystemUI/res/values-hi-rIN/smartspace_strings.xml b/packages/SystemUI/res/values-hi-rIN/smartspace_strings.xml new file mode 100644 index 00000000000..140001ddbc5 --- /dev/null +++ b/packages/SystemUI/res/values-hi-rIN/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Sekarang + diff --git a/packages/SystemUI/res/values-hr-rHR/smartspace_plurals.xml b/packages/SystemUI/res/values-hr-rHR/smartspace_plurals.xml new file mode 100644 index 00000000000..3bb976fa885 --- /dev/null +++ b/packages/SystemUI/res/values-hr-rHR/smartspace_plurals.xml @@ -0,0 +1,14 @@ + + + + + %d h + %d h + %d h + + + %d min + %d min + %d min + + diff --git a/packages/SystemUI/res/values-hr-rHR/smartspace_strings.xml b/packages/SystemUI/res/values-hr-rHR/smartspace_strings.xml new file mode 100644 index 00000000000..2832770ac00 --- /dev/null +++ b/packages/SystemUI/res/values-hr-rHR/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Sad + diff --git a/packages/SystemUI/res/values-hu-rHU/smartspace_plurals.xml b/packages/SystemUI/res/values-hu-rHU/smartspace_plurals.xml new file mode 100644 index 00000000000..a5ad39c7b2e --- /dev/null +++ b/packages/SystemUI/res/values-hu-rHU/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d óra + %d óra + + + %d perc + %d perc + + diff --git a/packages/SystemUI/res/values-hu-rHU/smartspace_strings.xml b/packages/SystemUI/res/values-hu-rHU/smartspace_strings.xml new file mode 100644 index 00000000000..5604b7f28ef --- /dev/null +++ b/packages/SystemUI/res/values-hu-rHU/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Most + diff --git a/packages/SystemUI/res/values-hy-rAM/smartspace_plurals.xml b/packages/SystemUI/res/values-hy-rAM/smartspace_plurals.xml new file mode 100644 index 00000000000..a47295f6752 --- /dev/null +++ b/packages/SystemUI/res/values-hy-rAM/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d ժ + %d ժ + + + %d ր + %d ր + + diff --git a/packages/SystemUI/res/values-hy-rAM/smartspace_strings.xml b/packages/SystemUI/res/values-hy-rAM/smartspace_strings.xml new file mode 100644 index 00000000000..03a8b73c168 --- /dev/null +++ b/packages/SystemUI/res/values-hy-rAM/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Հիմա + diff --git a/packages/SystemUI/res/values-in-rID/smartspace_plurals.xml b/packages/SystemUI/res/values-in-rID/smartspace_plurals.xml new file mode 100644 index 00000000000..9419e7ce67a --- /dev/null +++ b/packages/SystemUI/res/values-in-rID/smartspace_plurals.xml @@ -0,0 +1,10 @@ + + + + + %d jam + + + %d mnt + + diff --git a/packages/SystemUI/res/values-is-rIS/smartspace_plurals.xml b/packages/SystemUI/res/values-is-rIS/smartspace_plurals.xml new file mode 100644 index 00000000000..d6570922cdf --- /dev/null +++ b/packages/SystemUI/res/values-is-rIS/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d klst. + %d klst. + + + %d mín. + %d mín. + + diff --git a/packages/SystemUI/res/values-is-rIS/smartspace_strings.xml b/packages/SystemUI/res/values-is-rIS/smartspace_strings.xml new file mode 100644 index 00000000000..717e5bbeaa2 --- /dev/null +++ b/packages/SystemUI/res/values-is-rIS/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s og %2$s + Núna + diff --git a/packages/SystemUI/res/values-it-rIT/smartspace_plurals.xml b/packages/SystemUI/res/values-it-rIT/smartspace_plurals.xml new file mode 100644 index 00000000000..8597a44c759 --- /dev/null +++ b/packages/SystemUI/res/values-it-rIT/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d h + %d h + + + %d min + %d min + + diff --git a/packages/SystemUI/res/values-it-rIT/smartspace_strings.xml b/packages/SystemUI/res/values-it-rIT/smartspace_strings.xml new file mode 100644 index 00000000000..5a784ae48b4 --- /dev/null +++ b/packages/SystemUI/res/values-it-rIT/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Ora + diff --git a/packages/SystemUI/res/values-iw-rIL/smartspace_strings.xml b/packages/SystemUI/res/values-iw-rIL/smartspace_strings.xml new file mode 100644 index 00000000000..aee5725b269 --- /dev/null +++ b/packages/SystemUI/res/values-iw-rIL/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s ו-%2$s + עכשיו + diff --git a/packages/SystemUI/res/values-ja-rJP/smartspace_plurals.xml b/packages/SystemUI/res/values-ja-rJP/smartspace_plurals.xml new file mode 100644 index 00000000000..2853d12de66 --- /dev/null +++ b/packages/SystemUI/res/values-ja-rJP/smartspace_plurals.xml @@ -0,0 +1,10 @@ + + + + + %d 時間 + + + %d 分 + + diff --git a/packages/SystemUI/res/values-ja-rJP/smartspace_strings.xml b/packages/SystemUI/res/values-ja-rJP/smartspace_strings.xml new file mode 100644 index 00000000000..fd0d6efc953 --- /dev/null +++ b/packages/SystemUI/res/values-ja-rJP/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + たった今 + diff --git a/packages/SystemUI/res/values-ka-rGE/smartspace_plurals.xml b/packages/SystemUI/res/values-ka-rGE/smartspace_plurals.xml new file mode 100644 index 00000000000..4b2af19a259 --- /dev/null +++ b/packages/SystemUI/res/values-ka-rGE/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d სთ + %d სთ + + + %d წთ + %d წთ + + diff --git a/packages/SystemUI/res/values-ka-rGE/smartspace_strings.xml b/packages/SystemUI/res/values-ka-rGE/smartspace_strings.xml new file mode 100644 index 00000000000..f657cb54a92 --- /dev/null +++ b/packages/SystemUI/res/values-ka-rGE/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s და %2$s + ახლა + diff --git a/packages/SystemUI/res/values-kk-rKZ/smartspace_plurals.xml b/packages/SystemUI/res/values-kk-rKZ/smartspace_plurals.xml new file mode 100644 index 00000000000..84249a6be1f --- /dev/null +++ b/packages/SystemUI/res/values-kk-rKZ/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d сағ + %d сағ + + + %d мин + %d мин + + diff --git a/packages/SystemUI/res/values-kk-rKZ/smartspace_strings.xml b/packages/SystemUI/res/values-kk-rKZ/smartspace_strings.xml new file mode 100644 index 00000000000..f7d3e86b32f --- /dev/null +++ b/packages/SystemUI/res/values-kk-rKZ/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Қазір + diff --git a/packages/SystemUI/res/values-km-rKH/smartspace_plurals.xml b/packages/SystemUI/res/values-km-rKH/smartspace_plurals.xml new file mode 100644 index 00000000000..e5adc9d74bd --- /dev/null +++ b/packages/SystemUI/res/values-km-rKH/smartspace_plurals.xml @@ -0,0 +1,10 @@ + + + + + %d ម៉ + + + %d ន + + diff --git a/packages/SystemUI/res/values-km-rKH/smartspace_strings.xml b/packages/SystemUI/res/values-km-rKH/smartspace_strings.xml new file mode 100644 index 00000000000..3072a9f91f8 --- /dev/null +++ b/packages/SystemUI/res/values-km-rKH/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + ឥឡូវ​នេះ + diff --git a/packages/SystemUI/res/values-kn-rIN/smartspace_plurals.xml b/packages/SystemUI/res/values-kn-rIN/smartspace_plurals.xml new file mode 100644 index 00000000000..aebe512e4c6 --- /dev/null +++ b/packages/SystemUI/res/values-kn-rIN/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d ಗಂಟೆ + %d ಗಂಟೆ + + + %d ನಿಮಿಷ + %d ನಿಮಿಷ + + diff --git a/packages/SystemUI/res/values-kn-rIN/smartspace_strings.xml b/packages/SystemUI/res/values-kn-rIN/smartspace_strings.xml new file mode 100644 index 00000000000..8bf2f1fda4a --- /dev/null +++ b/packages/SystemUI/res/values-kn-rIN/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + ಈಗ + diff --git a/packages/SystemUI/res/values-ko-rKR/smartspace_plurals.xml b/packages/SystemUI/res/values-ko-rKR/smartspace_plurals.xml new file mode 100644 index 00000000000..737fadb8558 --- /dev/null +++ b/packages/SystemUI/res/values-ko-rKR/smartspace_plurals.xml @@ -0,0 +1,10 @@ + + + + + %d시간 + + + %d분 + + diff --git a/packages/SystemUI/res/values-ko-rKR/smartspace_strings.xml b/packages/SystemUI/res/values-ko-rKR/smartspace_strings.xml new file mode 100644 index 00000000000..b594f0e9f07 --- /dev/null +++ b/packages/SystemUI/res/values-ko-rKR/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + 방금 + diff --git a/packages/SystemUI/res/values-ky-rKG/smartspace_plurals.xml b/packages/SystemUI/res/values-ky-rKG/smartspace_plurals.xml new file mode 100644 index 00000000000..ebed98697db --- /dev/null +++ b/packages/SystemUI/res/values-ky-rKG/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d с. + %d с. + + + %d мүн. + %d мүн. + + diff --git a/packages/SystemUI/res/values-ky-rKG/smartspace_strings.xml b/packages/SystemUI/res/values-ky-rKG/smartspace_strings.xml new file mode 100644 index 00000000000..13286f0f683 --- /dev/null +++ b/packages/SystemUI/res/values-ky-rKG/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Азыр + diff --git a/packages/SystemUI/res/values-lo-rLA/smartspace_plurals.xml b/packages/SystemUI/res/values-lo-rLA/smartspace_plurals.xml new file mode 100644 index 00000000000..fcfca50f7d6 --- /dev/null +++ b/packages/SystemUI/res/values-lo-rLA/smartspace_plurals.xml @@ -0,0 +1,10 @@ + + + + + %d ຊມ + + + %d ນທ + + diff --git a/packages/SystemUI/res/values-lo-rLA/smartspace_strings.xml b/packages/SystemUI/res/values-lo-rLA/smartspace_strings.xml new file mode 100644 index 00000000000..5293aebe7c1 --- /dev/null +++ b/packages/SystemUI/res/values-lo-rLA/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + ຕອນນີ້ + diff --git a/packages/SystemUI/res/values-lt-rLT/smartspace_plurals.xml b/packages/SystemUI/res/values-lt-rLT/smartspace_plurals.xml new file mode 100644 index 00000000000..d352553050e --- /dev/null +++ b/packages/SystemUI/res/values-lt-rLT/smartspace_plurals.xml @@ -0,0 +1,16 @@ + + + + + %d valanda + %d valandos + %d valandos + %d valandų + + + %d minutė + %d minutės + %d minutės + %d minučių + + diff --git a/packages/SystemUI/res/values-lt-rLT/smartspace_strings.xml b/packages/SystemUI/res/values-lt-rLT/smartspace_strings.xml new file mode 100644 index 00000000000..6babdbaf789 --- /dev/null +++ b/packages/SystemUI/res/values-lt-rLT/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Dabar + diff --git a/packages/SystemUI/res/values-lv-rLV/smartspace_plurals.xml b/packages/SystemUI/res/values-lv-rLV/smartspace_plurals.xml new file mode 100644 index 00000000000..0ef35bac3ec --- /dev/null +++ b/packages/SystemUI/res/values-lv-rLV/smartspace_plurals.xml @@ -0,0 +1,14 @@ + + + + + %d h + %d h + %d h + + + %d min + %d min + %d min + + diff --git a/packages/SystemUI/res/values-lv-rLV/smartspace_strings.xml b/packages/SystemUI/res/values-lv-rLV/smartspace_strings.xml new file mode 100644 index 00000000000..98c57589c66 --- /dev/null +++ b/packages/SystemUI/res/values-lv-rLV/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Tikko + diff --git a/packages/SystemUI/res/values-mk-rMK/smartspace_plurals.xml b/packages/SystemUI/res/values-mk-rMK/smartspace_plurals.xml new file mode 100644 index 00000000000..109ea8acb18 --- /dev/null +++ b/packages/SystemUI/res/values-mk-rMK/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d час + %d часа + + + %d мин. + %d мин. + + diff --git a/packages/SystemUI/res/values-mk-rMK/smartspace_strings.xml b/packages/SystemUI/res/values-mk-rMK/smartspace_strings.xml new file mode 100644 index 00000000000..e0992621d0e --- /dev/null +++ b/packages/SystemUI/res/values-mk-rMK/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Сега + diff --git a/packages/SystemUI/res/values-mn-rMN/smartspace_plurals.xml b/packages/SystemUI/res/values-mn-rMN/smartspace_plurals.xml new file mode 100644 index 00000000000..def071ad7e6 --- /dev/null +++ b/packages/SystemUI/res/values-mn-rMN/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d цаг + %d цаг + + + %d мин + %d мин + + diff --git a/packages/SystemUI/res/values-mn-rMN/smartspace_strings.xml b/packages/SystemUI/res/values-mn-rMN/smartspace_strings.xml new file mode 100644 index 00000000000..dd7cc67b30c --- /dev/null +++ b/packages/SystemUI/res/values-mn-rMN/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Одоо + diff --git a/packages/SystemUI/res/values-mr-rIN/smartspace_plurals.xml b/packages/SystemUI/res/values-mr-rIN/smartspace_plurals.xml new file mode 100644 index 00000000000..ccd38bdbcf2 --- /dev/null +++ b/packages/SystemUI/res/values-mr-rIN/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d ता + %d ता + + + %d मि + %d मि + + diff --git a/packages/SystemUI/res/values-mr-rIN/smartspace_strings.xml b/packages/SystemUI/res/values-mr-rIN/smartspace_strings.xml new file mode 100644 index 00000000000..ce8bd59ee5d --- /dev/null +++ b/packages/SystemUI/res/values-mr-rIN/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + आता + diff --git a/packages/SystemUI/res/values-ms-rMY/smartspace_plurals.xml b/packages/SystemUI/res/values-ms-rMY/smartspace_plurals.xml new file mode 100644 index 00000000000..5e62e74301a --- /dev/null +++ b/packages/SystemUI/res/values-ms-rMY/smartspace_plurals.xml @@ -0,0 +1,10 @@ + + + + + %d jam + + + %d min + + diff --git a/packages/SystemUI/res/values-ms-rMY/smartspace_strings.xml b/packages/SystemUI/res/values-ms-rMY/smartspace_strings.xml new file mode 100644 index 00000000000..140001ddbc5 --- /dev/null +++ b/packages/SystemUI/res/values-ms-rMY/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Sekarang + diff --git a/packages/SystemUI/res/values-my-rMM/smartspace_plurals.xml b/packages/SystemUI/res/values-my-rMM/smartspace_plurals.xml new file mode 100644 index 00000000000..4bd604dc3f4 --- /dev/null +++ b/packages/SystemUI/res/values-my-rMM/smartspace_plurals.xml @@ -0,0 +1,10 @@ + + + + + %d နာရီ + + + %d မိနစ် + + diff --git a/packages/SystemUI/res/values-my-rMM/smartspace_strings.xml b/packages/SystemUI/res/values-my-rMM/smartspace_strings.xml new file mode 100644 index 00000000000..af4905a9c5b --- /dev/null +++ b/packages/SystemUI/res/values-my-rMM/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + ယခု + diff --git a/packages/SystemUI/res/values-nb-rNO/smartspace_plurals.xml b/packages/SystemUI/res/values-nb-rNO/smartspace_plurals.xml new file mode 100644 index 00000000000..085fc397106 --- /dev/null +++ b/packages/SystemUI/res/values-nb-rNO/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d t + %d t + + + %d min + %d min + + diff --git a/packages/SystemUI/res/values-nb-rNO/smartspace_strings.xml b/packages/SystemUI/res/values-nb-rNO/smartspace_strings.xml new file mode 100644 index 00000000000..7bea9b5af30 --- /dev/null +++ b/packages/SystemUI/res/values-nb-rNO/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + + diff --git a/packages/SystemUI/res/values-nl-rNL/smartspace_plurals.xml b/packages/SystemUI/res/values-nl-rNL/smartspace_plurals.xml new file mode 100644 index 00000000000..25917b0eb1d --- /dev/null +++ b/packages/SystemUI/res/values-nl-rNL/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d u + %d u + + + %d min + %d min + + diff --git a/packages/SystemUI/res/values-nl-rNL/smartspace_strings.xml b/packages/SystemUI/res/values-nl-rNL/smartspace_strings.xml new file mode 100644 index 00000000000..7f6ade0636d --- /dev/null +++ b/packages/SystemUI/res/values-nl-rNL/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Nu + diff --git a/packages/SystemUI/res/values-or-rIN/smartspace_plurals.xml b/packages/SystemUI/res/values-or-rIN/smartspace_plurals.xml new file mode 100644 index 00000000000..601f4eaf43c --- /dev/null +++ b/packages/SystemUI/res/values-or-rIN/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d ଘଣ୍ଟା + %d ଘଣ୍ଟା + + + %d ମିନିଟ୍‍ + %d ମିନିଟ୍‍ + + diff --git a/packages/SystemUI/res/values-or-rIN/smartspace_strings.xml b/packages/SystemUI/res/values-or-rIN/smartspace_strings.xml new file mode 100644 index 00000000000..4977a5f23bd --- /dev/null +++ b/packages/SystemUI/res/values-or-rIN/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + ବର୍ତ୍ତମାନ + diff --git a/packages/SystemUI/res/values-pa-rIN/smartspace_plurals.xml b/packages/SystemUI/res/values-pa-rIN/smartspace_plurals.xml new file mode 100644 index 00000000000..be68198fd21 --- /dev/null +++ b/packages/SystemUI/res/values-pa-rIN/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d ਘੰਟਾ + %d ਘੰਟੇ + + + %d ਮਿੰਟ + %d ਮਿੰਟ + + diff --git a/packages/SystemUI/res/values-pa-rIN/smartspace_strings.xml b/packages/SystemUI/res/values-pa-rIN/smartspace_strings.xml new file mode 100644 index 00000000000..2d50186d104 --- /dev/null +++ b/packages/SystemUI/res/values-pa-rIN/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + ਹੁਣੇ + diff --git a/packages/SystemUI/res/values-pl-rPL/smartspace_plurals.xml b/packages/SystemUI/res/values-pl-rPL/smartspace_plurals.xml new file mode 100644 index 00000000000..903dbfb4be7 --- /dev/null +++ b/packages/SystemUI/res/values-pl-rPL/smartspace_plurals.xml @@ -0,0 +1,16 @@ + + + + + %d godz. + %d godz. + %d godz. + %d godz. + + + %d min + %d min + %d min + %d min + + diff --git a/packages/SystemUI/res/values-pl-rPL/smartspace_strings.xml b/packages/SystemUI/res/values-pl-rPL/smartspace_strings.xml new file mode 100644 index 00000000000..4e1fad59441 --- /dev/null +++ b/packages/SystemUI/res/values-pl-rPL/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Teraz + diff --git a/packages/SystemUI/res/values-pt-rBR/smartspace_plurals.xml b/packages/SystemUI/res/values-pt-rBR/smartspace_plurals.xml new file mode 100644 index 00000000000..8597a44c759 --- /dev/null +++ b/packages/SystemUI/res/values-pt-rBR/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d h + %d h + + + %d min + %d min + + diff --git a/packages/SystemUI/res/values-pt-rBR/smartspace_strings.xml b/packages/SystemUI/res/values-pt-rBR/smartspace_strings.xml new file mode 100644 index 00000000000..4d228347097 --- /dev/null +++ b/packages/SystemUI/res/values-pt-rBR/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s e %2$s + Agora + diff --git a/packages/SystemUI/res/values-pt-rPT/smartspace_plurals.xml b/packages/SystemUI/res/values-pt-rPT/smartspace_plurals.xml new file mode 100644 index 00000000000..8597a44c759 --- /dev/null +++ b/packages/SystemUI/res/values-pt-rPT/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d h + %d h + + + %d min + %d min + + diff --git a/packages/SystemUI/res/values-pt-rPT/smartspace_strings.xml b/packages/SystemUI/res/values-pt-rPT/smartspace_strings.xml new file mode 100644 index 00000000000..4d228347097 --- /dev/null +++ b/packages/SystemUI/res/values-pt-rPT/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s e %2$s + Agora + diff --git a/packages/SystemUI/res/values-ro-rRO/smartspace_plurals.xml b/packages/SystemUI/res/values-ro-rRO/smartspace_plurals.xml new file mode 100644 index 00000000000..3bb976fa885 --- /dev/null +++ b/packages/SystemUI/res/values-ro-rRO/smartspace_plurals.xml @@ -0,0 +1,14 @@ + + + + + %d h + %d h + %d h + + + %d min + %d min + %d min + + diff --git a/packages/SystemUI/res/values-ro-rRO/smartspace_strings.xml b/packages/SystemUI/res/values-ro-rRO/smartspace_strings.xml new file mode 100644 index 00000000000..07089b3efc8 --- /dev/null +++ b/packages/SystemUI/res/values-ro-rRO/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Acum + diff --git a/packages/SystemUI/res/values-ru-rRU/smartspace_plurals.xml b/packages/SystemUI/res/values-ru-rRU/smartspace_plurals.xml new file mode 100644 index 00000000000..fe30cb2caa1 --- /dev/null +++ b/packages/SystemUI/res/values-ru-rRU/smartspace_plurals.xml @@ -0,0 +1,16 @@ + + + + + %d ч. + %d ч. + %d ч. + %d ч. + + + %d мин. + %d мин. + %d мин. + %d мин. + + diff --git a/packages/SystemUI/res/values-ru-rRU/smartspace_strings.xml b/packages/SystemUI/res/values-ru-rRU/smartspace_strings.xml new file mode 100644 index 00000000000..08d5dc28572 --- /dev/null +++ b/packages/SystemUI/res/values-ru-rRU/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Только что + diff --git a/packages/SystemUI/res/values-sk-rSK/smartspace_plurals.xml b/packages/SystemUI/res/values-sk-rSK/smartspace_plurals.xml new file mode 100644 index 00000000000..6723aee869c --- /dev/null +++ b/packages/SystemUI/res/values-sk-rSK/smartspace_plurals.xml @@ -0,0 +1,16 @@ + + + + + %d h + %d h + %d h + %d h + + + %d min + %d min + %d min + %d min + + diff --git a/packages/SystemUI/res/values-sk-rSK/smartspace_strings.xml b/packages/SystemUI/res/values-sk-rSK/smartspace_strings.xml new file mode 100644 index 00000000000..22380f98a7a --- /dev/null +++ b/packages/SystemUI/res/values-sk-rSK/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Teraz + diff --git a/packages/SystemUI/res/values-sl-rSI/smartspace_plurals.xml b/packages/SystemUI/res/values-sl-rSI/smartspace_plurals.xml new file mode 100644 index 00000000000..24cb4bb52b9 --- /dev/null +++ b/packages/SystemUI/res/values-sl-rSI/smartspace_plurals.xml @@ -0,0 +1,16 @@ + + + + + %d h + %d h + %d h + %d h + + + %d min + %d min + %d min + %d min + + diff --git a/packages/SystemUI/res/values-sl-rSI/smartspace_strings.xml b/packages/SystemUI/res/values-sl-rSI/smartspace_strings.xml new file mode 100644 index 00000000000..ee663aa7af9 --- /dev/null +++ b/packages/SystemUI/res/values-sl-rSI/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Zdaj + diff --git a/packages/SystemUI/res/values-sq-rAL/smartspace_plurals.xml b/packages/SystemUI/res/values-sq-rAL/smartspace_plurals.xml new file mode 100644 index 00000000000..14bec8f0388 --- /dev/null +++ b/packages/SystemUI/res/values-sq-rAL/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d orë + %d orë + + + %d min. + %d min. + + diff --git a/packages/SystemUI/res/values-sq-rAL/smartspace_strings.xml b/packages/SystemUI/res/values-sq-rAL/smartspace_strings.xml new file mode 100644 index 00000000000..c55b4df35a8 --- /dev/null +++ b/packages/SystemUI/res/values-sq-rAL/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Tani + diff --git a/packages/SystemUI/res/values-sr-rSP/smartspace_plurals.xml b/packages/SystemUI/res/values-sr-rSP/smartspace_plurals.xml new file mode 100644 index 00000000000..70d4bba0010 --- /dev/null +++ b/packages/SystemUI/res/values-sr-rSP/smartspace_plurals.xml @@ -0,0 +1,14 @@ + + + + + %d ч + %d ч + %d ч + + + %d мин + %d мин + %d мин + + diff --git a/packages/SystemUI/res/values-sr-rSP/smartspace_strings.xml b/packages/SystemUI/res/values-sr-rSP/smartspace_strings.xml new file mode 100644 index 00000000000..d8cff2b94a7 --- /dev/null +++ b/packages/SystemUI/res/values-sr-rSP/smartspace_strings.xml @@ -0,0 +1,7 @@ + + + + %1$s %2$s + Сада + %1$s %2$s + diff --git a/packages/SystemUI/res/values-sv-rSE/smartspace_plurals.xml b/packages/SystemUI/res/values-sv-rSE/smartspace_plurals.xml new file mode 100644 index 00000000000..49d67b20602 --- /dev/null +++ b/packages/SystemUI/res/values-sv-rSE/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d tim + %d tim + + + %d min + %d min + + diff --git a/packages/SystemUI/res/values-sv-rSE/smartspace_strings.xml b/packages/SystemUI/res/values-sv-rSE/smartspace_strings.xml new file mode 100644 index 00000000000..7f6ade0636d --- /dev/null +++ b/packages/SystemUI/res/values-sv-rSE/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Nu + diff --git a/packages/SystemUI/res/values-sw-rKE/smartspace_plurals.xml b/packages/SystemUI/res/values-sw-rKE/smartspace_plurals.xml new file mode 100644 index 00000000000..30bd83fced8 --- /dev/null +++ b/packages/SystemUI/res/values-sw-rKE/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + saa %d + saa %d + + + dakika %d + dakika %d + + diff --git a/packages/SystemUI/res/values-sw-rKE/smartspace_strings.xml b/packages/SystemUI/res/values-sw-rKE/smartspace_strings.xml new file mode 100644 index 00000000000..7b4487eda1d --- /dev/null +++ b/packages/SystemUI/res/values-sw-rKE/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Sasa + diff --git a/packages/SystemUI/res/values-ta-rIN/smartspace_plurals.xml b/packages/SystemUI/res/values-ta-rIN/smartspace_plurals.xml new file mode 100644 index 00000000000..04f91eacc9e --- /dev/null +++ b/packages/SystemUI/res/values-ta-rIN/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d ம.நே. + %d ம.நே. + + + %d நிமி. + %d நிமி. + + diff --git a/packages/SystemUI/res/values-ta-rIN/smartspace_strings.xml b/packages/SystemUI/res/values-ta-rIN/smartspace_strings.xml new file mode 100644 index 00000000000..1fc8d512eba --- /dev/null +++ b/packages/SystemUI/res/values-ta-rIN/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + இப்போது + diff --git a/packages/SystemUI/res/values-te-rIN/smartspace_plurals.xml b/packages/SystemUI/res/values-te-rIN/smartspace_plurals.xml new file mode 100644 index 00000000000..bf57043af64 --- /dev/null +++ b/packages/SystemUI/res/values-te-rIN/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d గం + %d గం + + + %d నిమి + %d నిమి + + diff --git a/packages/SystemUI/res/values-te-rIN/smartspace_strings.xml b/packages/SystemUI/res/values-te-rIN/smartspace_strings.xml new file mode 100644 index 00000000000..a52a8a697d4 --- /dev/null +++ b/packages/SystemUI/res/values-te-rIN/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + ఇప్పుడే + diff --git a/packages/SystemUI/res/values-th-rTH/smartspace_plurals.xml b/packages/SystemUI/res/values-th-rTH/smartspace_plurals.xml new file mode 100644 index 00000000000..8497e5b7c5a --- /dev/null +++ b/packages/SystemUI/res/values-th-rTH/smartspace_plurals.xml @@ -0,0 +1,10 @@ + + + + + %d ชม. + + + %d นาที + + diff --git a/packages/SystemUI/res/values-th-rTH/smartspace_strings.xml b/packages/SystemUI/res/values-th-rTH/smartspace_strings.xml new file mode 100644 index 00000000000..0f825bbaa9f --- /dev/null +++ b/packages/SystemUI/res/values-th-rTH/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + ตอนนี้ + diff --git a/packages/SystemUI/res/values-tl-rPH/smartspace_plurals.xml b/packages/SystemUI/res/values-tl-rPH/smartspace_plurals.xml new file mode 100644 index 00000000000..149b609acc8 --- /dev/null +++ b/packages/SystemUI/res/values-tl-rPH/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d oras + %d na oras + + + %d minuto + %d na minuto + + diff --git a/packages/SystemUI/res/values-tl-rPH/smartspace_strings.xml b/packages/SystemUI/res/values-tl-rPH/smartspace_strings.xml new file mode 100644 index 00000000000..b0f85608b0d --- /dev/null +++ b/packages/SystemUI/res/values-tl-rPH/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Ngayon + diff --git a/packages/SystemUI/res/values-tr-rTR/smartspace_plurals.xml b/packages/SystemUI/res/values-tr-rTR/smartspace_plurals.xml new file mode 100644 index 00000000000..8a4a94c7aa7 --- /dev/null +++ b/packages/SystemUI/res/values-tr-rTR/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d sa. + %d sa. + + + %d dk. + %d dk. + + diff --git a/packages/SystemUI/res/values-tr-rTR/smartspace_strings.xml b/packages/SystemUI/res/values-tr-rTR/smartspace_strings.xml new file mode 100644 index 00000000000..814c6e9f8a6 --- /dev/null +++ b/packages/SystemUI/res/values-tr-rTR/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Şimdi + diff --git a/packages/SystemUI/res/values-uk-rUA/smartspace_plurals.xml b/packages/SystemUI/res/values-uk-rUA/smartspace_plurals.xml new file mode 100644 index 00000000000..346f284a6ad --- /dev/null +++ b/packages/SystemUI/res/values-uk-rUA/smartspace_plurals.xml @@ -0,0 +1,16 @@ + + + + + %d год + %d год + %d год + %d год + + + %d хв + %d хв + %d хв + %d хв + + diff --git a/packages/SystemUI/res/values-uk-rUA/smartspace_strings.xml b/packages/SystemUI/res/values-uk-rUA/smartspace_strings.xml new file mode 100644 index 00000000000..c34a97f70fd --- /dev/null +++ b/packages/SystemUI/res/values-uk-rUA/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Щойно + diff --git a/packages/SystemUI/res/values-uz-rUZ/smartspace_plurals.xml b/packages/SystemUI/res/values-uz-rUZ/smartspace_plurals.xml new file mode 100644 index 00000000000..ba6080a4b72 --- /dev/null +++ b/packages/SystemUI/res/values-uz-rUZ/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d soat + %d soat + + + %d daqiqa + %d daqiqa + + diff --git a/packages/SystemUI/res/values-uz-rUZ/smartspace_strings.xml b/packages/SystemUI/res/values-uz-rUZ/smartspace_strings.xml new file mode 100644 index 00000000000..db855f567d8 --- /dev/null +++ b/packages/SystemUI/res/values-uz-rUZ/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Hozir + diff --git a/packages/SystemUI/res/values-vi-rVN/smartspace_plurals.xml b/packages/SystemUI/res/values-vi-rVN/smartspace_plurals.xml new file mode 100644 index 00000000000..edd4a9aa98b --- /dev/null +++ b/packages/SystemUI/res/values-vi-rVN/smartspace_plurals.xml @@ -0,0 +1,10 @@ + + + + + %d giờ + + + %d phút + + diff --git a/packages/SystemUI/res/values-vi-rVN/smartspace_strings.xml b/packages/SystemUI/res/values-vi-rVN/smartspace_strings.xml new file mode 100644 index 00000000000..58f579ac6bc --- /dev/null +++ b/packages/SystemUI/res/values-vi-rVN/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Hiện tại + diff --git a/packages/SystemUI/res/values-zh-rCN/smartspace_plurals.xml b/packages/SystemUI/res/values-zh-rCN/smartspace_plurals.xml new file mode 100644 index 00000000000..4646c2ae5d7 --- /dev/null +++ b/packages/SystemUI/res/values-zh-rCN/smartspace_plurals.xml @@ -0,0 +1,10 @@ + + + + + %d 小时 + + + %d 分钟 + + diff --git a/packages/SystemUI/res/values-zh-rCN/smartspace_strings.xml b/packages/SystemUI/res/values-zh-rCN/smartspace_strings.xml new file mode 100644 index 00000000000..7eb51a8f57b --- /dev/null +++ b/packages/SystemUI/res/values-zh-rCN/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + 现在 + diff --git a/packages/SystemUI/res/values-zh-rHK/smartspace_plurals.xml b/packages/SystemUI/res/values-zh-rHK/smartspace_plurals.xml new file mode 100644 index 00000000000..e0517f52ad0 --- /dev/null +++ b/packages/SystemUI/res/values-zh-rHK/smartspace_plurals.xml @@ -0,0 +1,10 @@ + + + + + %d 小時 + + + %d 分鐘 + + diff --git a/packages/SystemUI/res/values-zh-rHK/smartspace_strings.xml b/packages/SystemUI/res/values-zh-rHK/smartspace_strings.xml new file mode 100644 index 00000000000..d8518d19b90 --- /dev/null +++ b/packages/SystemUI/res/values-zh-rHK/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + 剛剛 + diff --git a/packages/SystemUI/res/values-zh-rSG/smartspace_plurals.xml b/packages/SystemUI/res/values-zh-rSG/smartspace_plurals.xml new file mode 100644 index 00000000000..e0517f52ad0 --- /dev/null +++ b/packages/SystemUI/res/values-zh-rSG/smartspace_plurals.xml @@ -0,0 +1,10 @@ + + + + + %d 小時 + + + %d 分鐘 + + diff --git a/packages/SystemUI/res/values-zh-rSG/smartspace_strings.xml b/packages/SystemUI/res/values-zh-rSG/smartspace_strings.xml new file mode 100644 index 00000000000..be332bc8cc6 --- /dev/null +++ b/packages/SystemUI/res/values-zh-rSG/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + 現在 + diff --git a/packages/SystemUI/res/values-zh-rTW/smartspace_plurals.xml b/packages/SystemUI/res/values-zh-rTW/smartspace_plurals.xml new file mode 100644 index 00000000000..e0517f52ad0 --- /dev/null +++ b/packages/SystemUI/res/values-zh-rTW/smartspace_plurals.xml @@ -0,0 +1,10 @@ + + + + + %d 小時 + + + %d 分鐘 + + diff --git a/packages/SystemUI/res/values-zh-rTW/smartspace_strings.xml b/packages/SystemUI/res/values-zh-rTW/smartspace_strings.xml new file mode 100644 index 00000000000..be332bc8cc6 --- /dev/null +++ b/packages/SystemUI/res/values-zh-rTW/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + 現在 + diff --git a/packages/SystemUI/res/values-zu-rZA/smartspace_plurals.xml b/packages/SystemUI/res/values-zu-rZA/smartspace_plurals.xml new file mode 100644 index 00000000000..417fa97bbc8 --- /dev/null +++ b/packages/SystemUI/res/values-zu-rZA/smartspace_plurals.xml @@ -0,0 +1,12 @@ + + + + + %d ihora + %d ihora + + + %d iminithi + %d iminithi + + diff --git a/packages/SystemUI/res/values-zu-rZA/smartspace_strings.xml b/packages/SystemUI/res/values-zu-rZA/smartspace_strings.xml new file mode 100644 index 00000000000..3e88ae05123 --- /dev/null +++ b/packages/SystemUI/res/values-zu-rZA/smartspace_strings.xml @@ -0,0 +1,6 @@ + + + + %1$s %2$s + Manje + diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index e851e81bc47..351e5bf6bcc 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -30,7 +30,7 @@ - com.android.systemui.statusbar.phone.StatusBar + com.google.android.systemui.statusbar.phone.StatusBarGoogle - wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,nfc,inversion,saver,dark,work,cast,night,caffeine,heads_up,sync,screenshot + wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,nfc,inversion,saver,dark,work,cast,night,caffeine,heads_up,sync,screenshot,reboot diff --git a/packages/SystemUI/res/values/reloaded_config.xml b/packages/SystemUI/res/values/reloaded_config.xml index bb424179aa0..01ae671a049 100644 --- a/packages/SystemUI/res/values/reloaded_config.xml +++ b/packages/SystemUI/res/values/reloaded_config.xml @@ -20,4 +20,7 @@ 4 5 + + false + diff --git a/packages/SystemUI/res/values/reloaded_dimens.xml b/packages/SystemUI/res/values/reloaded_dimens.xml index e2302eaca92..f7d07f11f12 100644 --- a/packages/SystemUI/res/values/reloaded_dimens.xml +++ b/packages/SystemUI/res/values/reloaded_dimens.xml @@ -18,4 +18,9 @@ 24.0dip + + 44dp + + + 54dp diff --git a/packages/SystemUI/res/values/reloaded_strings.xml b/packages/SystemUI/res/values/reloaded_strings.xml index fbc6084b40c..1aa09e0e364 100644 --- a/packages/SystemUI/res/values/reloaded_strings.xml +++ b/packages/SystemUI/res/values/reloaded_strings.xml @@ -128,4 +128,14 @@ Auto hide Hide the traffic monitor when there is no activity + + Reboot + Recovery + + +  is Locked. + , fingerprint or your face to unlock. +  or your face to unlock. +  or your fingerprint to unlock. + App is locked diff --git a/packages/SystemUI/res/values/smartspace_dimens.xml b/packages/SystemUI/res/values/smartspace_dimens.xml new file mode 100644 index 00000000000..0ef39d83d94 --- /dev/null +++ b/packages/SystemUI/res/values/smartspace_dimens.xml @@ -0,0 +1,3 @@ + + 1dp + diff --git a/packages/SystemUI/res/values/smartspace_plurals.xml b/packages/SystemUI/res/values/smartspace_plurals.xml new file mode 100644 index 00000000000..8197ea806c3 --- /dev/null +++ b/packages/SystemUI/res/values/smartspace_plurals.xml @@ -0,0 +1,10 @@ + + + %d hr + %d hr + + + %d min + %d min + + diff --git a/packages/SystemUI/res/values/smartspace_strings.xml b/packages/SystemUI/res/values/smartspace_strings.xml new file mode 100644 index 00000000000..bebf129f609 --- /dev/null +++ b/packages/SystemUI/res/values/smartspace_strings.xml @@ -0,0 +1,5 @@ + + %1$s %2$s + Now + %1$s: %2$s + diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 89d13ede603..ece84f6b0e5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -263,6 +263,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { private boolean mLockIconPressed; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private final boolean mFaceAuthOnlyOnSecurityView; + /** * Short delay before restarting biometric authentication after a successful try * This should be slightly longer than the time between onAuthenticated @@ -1541,6 +1543,8 @@ protected KeyguardUpdateMonitor(Context context) { mSubscriptionManager = SubscriptionManager.from(context); mDeviceProvisioned = isDeviceProvisionedInSettingsDb(); mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged); + mFaceAuthOnlyOnSecurityView = mContext.getResources().getBoolean( + R.bool.config_faceAuthOnlyOnSecurityView); // Since device can't be un-provisioned, we only need to register a content observer // to update mDeviceProvisioned when we are... @@ -1745,7 +1749,7 @@ private boolean shouldListenForFingerprint() { * If face auth is allows to scan on this exact moment. */ public boolean shouldListenForFace() { - final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep; + boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep; final int user = getCurrentUser(); final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user); final boolean isLockOutOrLockDown = @@ -1768,13 +1772,19 @@ public boolean shouldListenForFace() { boolean strongAuthAllowsScanning = (!isEncryptedOrTimedOut || canBypass && !mBouncer) && !isLockOutOrLockDown; + boolean unlockPossible = true; + if ((!mBouncer || !awakeKeyguard) && mFaceAuthOnlyOnSecurityView){ + unlockPossible = false; + } + // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware. return (mBouncer || mAuthInterruptActive || awakeKeyguard || shouldListenForFaceAssistant()) && !mSwitchingUser && !isFaceDisabled(user) && becauseCannotSkipBouncer && !mKeyguardGoingAway && mFaceSettingEnabledForUser.get(user) && !mLockIconPressed && strongAuthAllowsScanning && mIsPrimaryUser - && !mSecureCameraLaunched; + && !mSecureCameraLaunched + && unlockPossible; } /** diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/SystemUIDefaultModule.java index 262b5ec50d8..0f496224d74 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIDefaultModule.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIDefaultModule.java @@ -59,10 +59,6 @@ static String provideLeakReportEmail() { @Binds abstract EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates); - @Binds - abstract NotificationLockscreenUserManager bindNotificationLockscreenUserManager( - NotificationLockscreenUserManagerImpl notificationLockscreenUserManager); - @Binds abstract DockManager bindDockManager(DockManagerImpl dockManager); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java index a5b386eddd3..c10d3767b92 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java @@ -24,6 +24,8 @@ import com.android.systemui.util.InjectionInflationController; import com.android.systemui.util.leak.GarbageMonitor; +import com.google.android.systemui.SystemUIGoogleModule; + import javax.inject.Named; import javax.inject.Singleton; @@ -39,7 +41,8 @@ ServiceBinder.class, SystemUIFactory.ContextHolder.class, SystemUIModule.class, - SystemUIDefaultModule.class}) + SystemUIDefaultModule.class, + SystemUIGoogleModule.class}) public interface SystemUIRootComponent { /** diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java index e66a8fa9629..c66a01cb94a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java @@ -59,6 +59,7 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba private WindowManager mWindowManager; private IBiometricServiceReceiverInternal mReceiver; private boolean mDialogShowing; + private boolean mTryAgainPressed; private Callback mCallback = new Callback(); private WakefulnessLifecycle mWakefulnessLifecycle; @@ -73,7 +74,8 @@ public void handleMessage(Message msg) { case MSG_BIOMETRIC_AUTHENTICATED: { SomeArgs args = (SomeArgs) msg.obj; handleBiometricAuthenticated((boolean) args.arg1 /* authenticated */, - (String) args.arg2 /* failureReason */); + (String) args.arg2 /* failureReason */, + (boolean) args.arg3); args.recycle(); break; } @@ -181,13 +183,14 @@ public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal } @Override - public void onBiometricAuthenticated(boolean authenticated, String failureReason) { + public void onBiometricAuthenticated(boolean authenticated, String failureReason, boolean requireConfirmation) { if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated + " reason: " + failureReason); SomeArgs args = SomeArgs.obtain(); args.arg1 = authenticated; args.arg2 = failureReason; + args.arg3 = requireConfirmation; mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, args).sendToTarget(); } @@ -216,14 +219,20 @@ private void handleShowDialog(SomeArgs args, boolean skipAnimation, Bundle saved final int type = args.argi1; // Create a new dialog but do not replace the current one yet. - BiometricDialogView newDialog; - if (type == BiometricAuthenticator.TYPE_FINGERPRINT) { - newDialog = new FingerprintDialogView(mContext, mCallback); - } else if (type == BiometricAuthenticator.TYPE_FACE) { - newDialog = new FaceDialogView(mContext, mCallback); - } else { - Log.e(TAG, "Unsupported type: " + type); - return; + BiometricDialogView newDialog = mCurrentDialog; + final boolean isFingerprint = (type & BiometricAuthenticator.TYPE_FINGERPRINT) != 0; + final boolean isFace = (type & BiometricAuthenticator.TYPE_FACE) != 0; + if (!mTryAgainPressed) { + if (isFace && isFingerprint) { + newDialog = new FingerprintAndFaceDialogView(mContext, mCallback); + } else if (isFingerprint) { + newDialog = new FingerprintDialogView(mContext, mCallback); + } else if (isFace) { + newDialog = new FaceDialogView(mContext, mCallback); + } else { + Log.e(TAG, "Unsupported type: " + type); + return; + } } if (DEBUG) Log.d(TAG, "handleShowDialog, " @@ -236,32 +245,38 @@ private void handleShowDialog(SomeArgs args, boolean skipAnimation, Bundle saved // SavedState is only non-null if it's from onConfigurationChanged. Restore the state // even though it may be removed / re-created again newDialog.restoreState(savedState); - } else if (mCurrentDialog != null && mDialogShowing) { + } else if (mCurrentDialog != null && mDialogShowing && !mTryAgainPressed) { // If somehow we're asked to show a dialog, the old one doesn't need to be animated // away. This can happen if the app cancels and re-starts auth during configuration // change. This is ugly because we also have to do things on onConfigurationChanged // here. mCurrentDialog.forceRemove(); + mDialogShowing = false; } + newDialog.setFaceAndFingerprint(isFace, isFingerprint); mReceiver = (IBiometricServiceReceiverInternal) args.arg2; newDialog.setBundle((Bundle) args.arg1); newDialog.setRequireConfirmation((boolean) args.arg3); newDialog.setUserId(args.argi2); newDialog.setSkipIntro(skipAnimation); mCurrentDialog = newDialog; - mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams()); + if (!mTryAgainPressed && !mDialogShowing) { + mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams()); + } mDialogShowing = true; + mTryAgainPressed = false; } - private void handleBiometricAuthenticated(boolean authenticated, String failureReason) { - if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated: " + authenticated); + private void handleBiometricAuthenticated(boolean authenticated, String failureReason, boolean requireConfirmation) { + if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated: " + authenticated + + " requireConfirmation: " + requireConfirmation); if (authenticated) { mCurrentDialog.announceForAccessibility( mContext.getResources() .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId())); - if (mCurrentDialog.requiresConfirmation()) { + if (requireConfirmation) { mCurrentDialog.updateState(BiometricDialogView.STATE_PENDING_CONFIRMATION); } else { mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED); @@ -305,6 +320,7 @@ private void handleHideDialog(boolean userCanceled) { } mReceiver = null; mDialogShowing = false; + mTryAgainPressed = false; mCurrentDialog.startDismiss(); } @@ -340,6 +356,7 @@ private void handleUserCanceled() { private void handleTryAgainPressed() { try { + mTryAgainPressed = true; mReceiver.onTryAgainPressed(); } catch (RemoteException e) { Log.e(TAG, "RemoteException when handling try again", e); @@ -360,6 +377,7 @@ protected void onConfigurationChanged(Configuration newConfig) { if (mDialogShowing) { mCurrentDialog.forceRemove(); mDialogShowing = false; + mTryAgainPressed = false; } if (wasShowing) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java index cbbd3a0b056..8bea276764e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java @@ -17,9 +17,16 @@ package com.android.systemui.biometrics; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE; +import static android.view.Gravity.CENTER; +import static android.view.Gravity.START; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import android.app.admin.DevicePolicyManager; import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; @@ -29,7 +36,9 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; @@ -65,6 +74,7 @@ public abstract class BiometricDialogView extends LinearLayout { private static final String KEY_ERROR_TEXT_STRING = "key_error_text_string"; private static final String KEY_ERROR_TEXT_IS_TEMPORARY = "key_error_text_is_temporary"; private static final String KEY_ERROR_TEXT_COLOR = "key_error_text_color"; + private static final String FOD = "vendor.pa.biometrics.fingerprint.inscreen"; private static final int ANIMATION_DURATION_SHOW = 250; // ms private static final int ANIMATION_DURATION_AWAY = 350; // ms @@ -87,6 +97,7 @@ public abstract class BiometricDialogView extends LinearLayout { private final int mErrorColor; private final float mDialogWidth; protected final DialogViewCallback mCallback; + private final PackageManager mPackageManager; protected final ViewGroup mLayout; protected final LinearLayout mDialog; @@ -94,6 +105,7 @@ public abstract class BiometricDialogView extends LinearLayout { protected final TextView mSubtitleText; protected final TextView mDescriptionText; protected final ImageView mBiometricIcon; + protected final ImageView mAppIcon; protected final TextView mErrorText; protected final Button mPositiveButton; protected final Button mNegativeButton; @@ -108,8 +120,12 @@ public abstract class BiometricDialogView extends LinearLayout { private boolean mAnimatingAway; private boolean mWasForceRemoved; private boolean mSkipIntro; + protected boolean mAppLockDialog; + private boolean mIsFingerprint; + private boolean mIsFace; protected boolean mRequireConfirmation; private int mUserId; // used to determine if we should show work background + private final boolean mHasFod; private boolean mCompletedAnimatingIn; private boolean mPendingDismissDialog; @@ -135,7 +151,6 @@ public void run() { .translationY(0) .setDuration(ANIMATION_DURATION_SHOW) .setInterpolator(mLinearOutSlowIn) - .withLayer() .withEndAction(() -> onDialogAnimatedIn()) .start(); } @@ -163,10 +178,12 @@ public BiometricDialogView(Context context, DialogViewCallback callback) { mWindowManager = mContext.getSystemService(WindowManager.class); mUserManager = mContext.getSystemService(UserManager.class); mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class); + mPackageManager = mContext.getPackageManager(); mAnimationTranslationOffset = getResources() .getDimension(R.dimen.biometric_dialog_animation_translation_offset); mErrorColor = getResources().getColor(R.color.biometric_dialog_error); mTextColor = getResources().getColor(R.color.biometric_dialog_gray); + mHasFod = mPackageManager.hasSystemFeature(FOD); DisplayMetrics metrics = new DisplayMetrics(); mWindowManager.getDefaultDisplay().getMetrics(metrics); @@ -210,6 +227,25 @@ public boolean onKey(View v, int keyCode, KeyEvent event) { mPositiveButton = mLayout.findViewById(R.id.button1); mTryAgainButton = mLayout.findViewById(R.id.button_try_again); + mAppIcon = new ImageView(context); + final int iconDim = getResources().getDimensionPixelSize( + R.dimen.applock_icon_dimension); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(iconDim, iconDim); + lp.gravity = CENTER; + lp.topMargin = -iconDim/2; + lp.bottomMargin = iconDim/4; + mAppIcon.setLayoutParams(lp); + mAppIcon.setVisibility(View.GONE); + mDialog.addView(mAppIcon, 0); + ((ViewGroup) mDialog.getParent()).setClipChildren(false); + ((ViewGroup) mDialog.getParent().getParent()).setClipChildren(false); + ((ViewGroup) mDialog.getParent().getParent().getParent()).setClipChildren(false); + + lp = (LinearLayout.LayoutParams) mDialog.getLayoutParams(); + lp.leftMargin = 0; + lp.bottomMargin = 0; + lp.rightMargin = 0; + mBiometricIcon.setContentDescription( getResources().getString(getIconDescriptionResourceId())); @@ -299,10 +335,33 @@ public void onAttachedToWindow() { updateState(mState); } - CharSequence titleText = mBundle.getCharSequence(BiometricPrompt.KEY_TITLE); - - mTitleText.setVisibility(View.VISIBLE); - mTitleText.setText(titleText); + final CharSequence applockPackage = mBundle.getCharSequence(BiometricPrompt.KEY_APPLOCK_PKG); + final CharSequence titleText = mBundle.getCharSequence(BiometricPrompt.KEY_TITLE); + if (TextUtils.isEmpty(applockPackage)) { + mAppLockDialog = false; + mTitleText.setVisibility(View.VISIBLE); + mTitleText.setText(titleText); + mAppIcon.setVisibility(View.GONE); + mDescriptionText.setGravity(START); + } else { + mAppLockDialog = true; + ApplicationInfo aInfo = null; + try { + aInfo = mPackageManager.getApplicationInfoAsUser(applockPackage.toString(), 0, mUserId); + } catch(PackageManager.NameNotFoundException e) { + } + Drawable icon = (aInfo == null) ? null : mPackageManager.getApplicationIcon(aInfo); + if (icon == null) { + mTitleText.setVisibility(View.VISIBLE); + mTitleText.setText("Unlock " + titleText.toString()); + mAppIcon.setVisibility(View.GONE); + } else { + mTitleText.setVisibility(View.GONE); + mAppIcon.setVisibility(View.VISIBLE); + mAppIcon.setImageDrawable(icon); + } + mDescriptionText.setGravity(CENTER); + } final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE); if (TextUtils.isEmpty(subtitleText)) { @@ -313,13 +372,18 @@ public void onAttachedToWindow() { mSubtitleText.setText(subtitleText); } - final CharSequence descriptionText = + CharSequence descriptionText = mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION); if (TextUtils.isEmpty(descriptionText)) { mDescriptionText.setVisibility(View.GONE); announceAccessibilityEvent(); } else { mDescriptionText.setVisibility(View.VISIBLE); + if (mAppLockDialog) { + final CharSequence negText = mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT); + descriptionText += getResources().getString(R.string.applock_locked) + "\n" + + negText + getResources().getString(getDescriptionTextId()); + } mDescriptionText.setText(descriptionText); } @@ -335,7 +399,7 @@ public void onAttachedToWindow() { mDialog.setAlpha(1.0f); mDialog.setTranslationY(0); mLayout.setAlpha(1.0f); - mCompletedAnimatingIn = true; + onDialogAnimatedIn(); } else { // Dim the background and slide the dialog up mDialog.setTranslationY(mAnimationTranslationOffset); @@ -346,6 +410,53 @@ public void onAttachedToWindow() { mSkipIntro = false; } + private int getDescriptionTextId() { + if (mIsFingerprint && mIsFace) { + return R.string.applock_fingerprint_face; + } else if (mIsFace) { + return R.string.applock_face; + } else { + return R.string.applock_fingerprint; + } + } + + protected int getAnimatingAwayDuration() { + return (int) ((mAppLockDialog ? 1.3f : 1f ) * (float) ANIMATION_DURATION_AWAY); + } + + public void setFaceAndFingerprint(boolean isFace, boolean isFingerprint) { + mIsFace = isFace; + mIsFingerprint = isFingerprint; + if (mIsFingerprint) { + mBiometricIcon.setVisibility(mHasFod ? View.INVISIBLE : View.VISIBLE); + boolean isPortrait = (getResources().getConfiguration().orientation + == Configuration.ORIENTATION_PORTRAIT); + if (mHasFod && isPortrait) { + boolean isGesturalNav= Integer.parseInt(Settings.Secure.getStringForUser( + mContext.getContentResolver(), Settings.Secure.NAVIGATION_MODE, + UserHandle.USER_CURRENT)) == NAV_BAR_MODE_GESTURAL; + + final int navbarHeight = getResources().getDimensionPixelSize( + com.android.internal.R.dimen.navigation_bar_height); + final int fodMargin = getResources().getDimensionPixelSize( + R.dimen.biometric_dialog_fod_margin); + + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mBiometricIcon.getLayoutParams(); + lp.topMargin = isGesturalNav ? fodMargin : (fodMargin > navbarHeight) + ? (fodMargin - navbarHeight) : 0; + + // Add Errortext above the biometric icon + mDialog.removeView(mErrorText); + mDialog.addView(mErrorText, mDialog.indexOfChild(mBiometricIcon)); + lp = (LinearLayout.LayoutParams) mDescriptionText.getLayoutParams(); + lp.bottomMargin = mErrorText.getPaddingTop(); + mErrorText.setPadding(0, 0, 0, 0); + } + } else if (mIsFace) { + mBiometricIcon.setVisibility(View.VISIBLE); + } + } + private void setDismissesDialog(View v) { v.setClickable(true); v.setOnClickListener(v1 -> { @@ -382,15 +493,14 @@ public void run() { public void run() { mLayout.animate() .alpha(0f) - .setDuration(ANIMATION_DURATION_AWAY) + .setDuration(getAnimatingAwayDuration()) .setInterpolator(mLinearOutSlowIn) .withLayer() .start(); mDialog.animate() .translationY(mAnimationTranslationOffset) - .setDuration(ANIMATION_DURATION_AWAY) + .setDuration(getAnimatingAwayDuration()) .setInterpolator(mLinearOutSlowIn) - .withLayer() .withEndAction(endActionRunnable) .start(); } @@ -555,9 +665,13 @@ public WindowManager.LayoutParams getLayoutParams() { WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, PixelFormat.TRANSLUCENT); - lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS + | WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; lp.setTitle("BiometricDialogView"); lp.token = mWindowToken; + if (mHasFod) { + lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + } return lp; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java index ae6cb5ce23d..73725a640c7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java @@ -165,7 +165,7 @@ private void updateSize(int newSize) { if (newSize == SIZE_SMALL) { // These fields are required and/or always hold a spot on the UI, so should be set to // INVISIBLE so they keep their position - mTitleText.setVisibility(View.INVISIBLE); + mTitleText.setVisibility(mAppLockDialog ? View.GONE : View.INVISIBLE); mErrorText.setVisibility(View.INVISIBLE); mNegativeButton.setVisibility(View.INVISIBLE); @@ -247,7 +247,7 @@ private void updateSize(int newSize) { public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); // Set the visibility of opacity-animating views back to VISIBLE - mTitleText.setVisibility(View.VISIBLE); + if (!mAppLockDialog) mTitleText.setVisibility(View.VISIBLE); mErrorText.setVisibility(View.VISIBLE); mNegativeButton.setVisibility(View.VISIBLE); mTryAgainButton.setVisibility(View.VISIBLE); @@ -264,6 +264,7 @@ public void onAnimationStart(Animator animation) { public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mSize = SIZE_BIG; + updateSize(mSize); } }); as.play(outlineAnimator).with(iconAnimator).with(opacityAnimator) @@ -286,7 +287,6 @@ public void onSaveState(Bundle bundle) { bundle.putBoolean(KEY_DIALOG_ANIMATED_IN, mDialogAnimatedIn); } - @Override protected void handleResetMessage() { mErrorText.setTextColor(mTextColor); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintAndFaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintAndFaceDialogView.java new file mode 100644 index 00000000000..a7da4947de3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintAndFaceDialogView.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.biometrics; + +import static android.view.Gravity.CENTER_HORIZONTAL; +import static android.view.Gravity.TOP; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Outline; +import android.graphics.drawable.Animatable2; +import android.graphics.drawable.AnimatedVectorDrawable; +import android.graphics.drawable.Drawable; +import android.hardware.biometrics.BiometricPrompt; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; +import android.widget.FrameLayout; + +import com.android.systemui.R; + +/** + * This class loads the view for the system-provided dialog. The view consists of: + * Application Icon, Title, Subtitle, Description, Biometric Icon, Error/Help message area, + * and positive/negative buttons. + */ +public class FingerprintAndFaceDialogView extends BiometricDialogView { + + private static final String TAG = "FingerprintAndFaceDialogView"; + private static final String KEY_DIALOG_ANIMATED_IN = "key_dialog_animated_in"; + + private static final int HIDE_DIALOG_DELAY = 200; // ms + + private IconController mIconController; + private ImageView mFaceIcon; + private boolean mDialogAnimatedIn; + + /** + * Class that handles the biometric icon animations. + */ + private final class IconController extends Animatable2.AnimationCallback { + + private boolean mLastPulseDirection; // false = dark to light, true = light to dark + + int mState; + + IconController() { + mState = STATE_IDLE; + } + + public void animateOnce(int iconRes) { + animateIcon(iconRes, false); + } + + public void showStatic(int iconRes) { + mFaceIcon.setImageDrawable(mContext.getDrawable(iconRes)); + } + + public void startPulsing() { + mLastPulseDirection = false; + animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true); + } + + public void showIcon(int iconRes) { + final Drawable drawable = mContext.getDrawable(iconRes); + mFaceIcon.setImageDrawable(drawable); + } + + private void animateIcon(int iconRes, boolean repeat) { + final AnimatedVectorDrawable icon = + (AnimatedVectorDrawable) mContext.getDrawable(iconRes); + mFaceIcon.setImageDrawable(icon); + icon.forceAnimationOnUI(); + if (repeat) { + icon.registerAnimationCallback(this); + } + icon.start(); + } + + private void pulseInNextDirection() { + int iconRes = mLastPulseDirection ? R.drawable.face_dialog_pulse_dark_to_light + : R.drawable.face_dialog_pulse_light_to_dark; + animateIcon(iconRes, true /* repeat */); + mLastPulseDirection = !mLastPulseDirection; + } + + @Override + public void onAnimationEnd(Drawable drawable) { + super.onAnimationEnd(drawable); + + if (mState == STATE_AUTHENTICATING) { + // Still authenticating, pulse the icon + pulseInNextDirection(); + } + } + } + + private final Runnable mErrorToIdleAnimationRunnable = () -> { + updateState(STATE_IDLE); + announceAccessibilityEvent(); + }; + + public FingerprintAndFaceDialogView(Context context, + DialogViewCallback callback) { + super(context, callback); + + mIconController = new IconController(); + mFaceIcon = new ImageView(context); + final int iconDim = getResources().getDimensionPixelSize( + R.dimen.biometric_dialog_biometric_icon_size); + mFaceIcon.setVisibility(View.VISIBLE); + mLayout.addView(mFaceIcon); + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mFaceIcon.getLayoutParams(); + lp.gravity = TOP | CENTER_HORIZONTAL; + lp.width = iconDim; + lp.height = iconDim; + lp.topMargin = iconDim; + } + + @Override + public void onSaveState(Bundle bundle) { + super.onSaveState(bundle); + bundle.putBoolean(KEY_DIALOG_ANIMATED_IN, mDialogAnimatedIn); + } + + @Override + protected void handleResetMessage() { + mErrorText.setText(getHintStringResourceId()); + mErrorText.setTextColor(mTextColor); + announceAccessibilityEvent(); + } + + @Override + public void restoreState(Bundle bundle) { + super.restoreState(bundle); + // Keep in mind that this happens before onAttachedToWindow() + mDialogAnimatedIn = bundle.getBoolean(KEY_DIALOG_ANIMATED_IN); + } + + @Override + public void onAuthenticationFailed(String message) { + super.onAuthenticationFailed(message); + showTryAgainButton(true); + } + + @Override + public void showTryAgainButton(boolean show) { + if (show) { + mTryAgainButton.setVisibility(View.VISIBLE); + mPositiveButton.setVisibility(View.GONE); + announceAccessibilityEvent(); + } else { + mTryAgainButton.setVisibility(View.GONE); + announceAccessibilityEvent(); + } + } + + @Override + protected int getHintStringResourceId() { + return R.string.fingerprint_dialog_touch_sensor; + } + + @Override + protected int getAuthenticatedAccessibilityResourceId() { + return com.android.internal.R.string.fingerprint_authenticated; + } + + @Override + protected int getIconDescriptionResourceId() { + return R.string.accessibility_fingerprint_dialog_fingerprint_icon; + } + + @Override + protected void updateIcon(int oldState, int newState) { + mIconController.mState = newState; + + if (newState == STATE_AUTHENTICATING) { + mHandler.removeCallbacks(mErrorToIdleAnimationRunnable); + if (mDialogAnimatedIn) { + mIconController.startPulsing(); + } else { + mIconController.showIcon(R.drawable.face_dialog_pulse_dark_to_light); + } + mFaceIcon.setContentDescription(mContext.getString( + R.string.biometric_dialog_face_icon_description_authenticating)); + } else if (oldState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) { + mIconController.animateOnce(R.drawable.face_dialog_dark_to_checkmark); + mFaceIcon.setContentDescription(mContext.getString( + R.string.biometric_dialog_face_icon_description_confirmed)); + } else if (oldState == STATE_ERROR && newState == STATE_IDLE) { + mIconController.animateOnce(R.drawable.face_dialog_error_to_idle); + mFaceIcon.setContentDescription(mContext.getString( + R.string.biometric_dialog_face_icon_description_idle)); + } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATED) { + mHandler.removeCallbacks(mErrorToIdleAnimationRunnable); + mIconController.animateOnce(R.drawable.face_dialog_dark_to_checkmark); + mFaceIcon.setContentDescription(mContext.getString( + R.string.biometric_dialog_face_icon_description_authenticated)); + } else if (newState == STATE_ERROR) { + // It's easier to only check newState and gate showing the animation on the + // mErrorToIdleAnimationRunnable as a proxy, than add a ton of extra state. For example, + // we may go from error -> error due to configuration change which is valid and we + // should show the animation, or we can go from error -> error by receiving repeated + // acquire messages in which case we do not want to repeatedly start the animation. + if (!mHandler.hasCallbacks(mErrorToIdleAnimationRunnable)) { + mIconController.animateOnce(R.drawable.face_dialog_dark_to_error); + mHandler.postDelayed(mErrorToIdleAnimationRunnable, + BiometricPrompt.HIDE_DIALOG_DELAY); + } + } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) { + mIconController.animateOnce(R.drawable.face_dialog_dark_to_checkmark); + mFaceIcon.setContentDescription(mContext.getString( + R.string.biometric_dialog_face_icon_description_authenticated)); + } else if (newState == STATE_PENDING_CONFIRMATION) { + mHandler.removeCallbacks(mErrorToIdleAnimationRunnable); + mIconController.animateOnce(R.drawable.face_dialog_wink_from_dark); + mFaceIcon.setContentDescription(mContext.getString( + R.string.biometric_dialog_face_icon_description_authenticated)); + } else if (newState == STATE_IDLE) { + mIconController.showStatic(R.drawable.face_dialog_idle_static); + mFaceIcon.setContentDescription(mContext.getString( + R.string.biometric_dialog_face_icon_description_idle)); + } else { + Log.w(TAG, "Unknown animation from " + oldState + " -> " + newState); + } + + final Drawable icon = mContext.getDrawable(R.drawable.fingerprint_dialog_fp_to_error); + mBiometricIcon.setImageDrawable(icon); + + // Note that this must be after the newState == STATE_ERROR check above since this affects + // the logic. + if (oldState == STATE_ERROR && newState == STATE_ERROR) { + // Keep the error icon and text around for a while longer if we keep receiving + // STATE_ERROR + mHandler.removeCallbacks(mErrorToIdleAnimationRunnable); + mHandler.postDelayed(mErrorToIdleAnimationRunnable, BiometricPrompt.HIDE_DIALOG_DELAY); + } + } + + @Override + public void onDialogAnimatedIn() { + super.onDialogAnimatedIn(); + mDialogAnimatedIn = true; + mIconController.startPulsing(); + } + + @Override + protected int getDelayAfterAuthenticatedDurationMs() { + return HIDE_DIALOG_DELAY; + } + + @Override + protected boolean shouldGrayAreaDismissDialog() { + return true; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index b3d9ee03302..34d1611de49 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -711,7 +711,11 @@ public void onPress() { mHandler.postDelayed(new Runnable() { @Override public void run() { - mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null); + try { + WindowManagerGlobal.getWindowManagerService().takeOPScreenshot(1); + } catch (RemoteException e) { + Log.e(TAG, "Error while trying to take screenshot.", e); + } MetricsLogger.action(mContext, MetricsEvent.ACTION_SCREENSHOT_POWER_MENU); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 7ab032e7979..1a27a682fee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -42,6 +42,7 @@ import com.android.systemui.qs.tiles.LocationTile; import com.android.systemui.qs.tiles.NfcTile; import com.android.systemui.qs.tiles.NightDisplayTile; +import com.android.systemui.qs.tiles.RebootTile; import com.android.systemui.qs.tiles.RotationLockTile; import com.android.systemui.qs.tiles.SyncTile; import com.android.systemui.qs.tiles.ScreenshotTile; @@ -194,6 +195,8 @@ private QSTileImpl createTileInternal(String tileSpec) { return mSyncTileProvider.get(); case "screenshot": return mScreenshotTileProvider.get(); + case "reboot": + return new RebootTile(mHost); } // Intent tiles. diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RebootTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RebootTile.java new file mode 100644 index 00000000000..f85175ffc2c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RebootTile.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2013 Slimroms + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles; + +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.PowerManager; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.R; +import com.android.systemui.plugins.qs.QSTile.BooleanState; +import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.tileimpl.QSTileImpl; + +public class RebootTile extends QSTileImpl { + + private boolean mRebootToRecovery = false; + + public RebootTile(QSHost host) { + super(host); + } + + @Override + public BooleanState newTileState() { + return new BooleanState(); + } + + @Override + public void handleClick() { + mRebootToRecovery = !mRebootToRecovery; + refreshState(); + } + + @Override + protected void handleLongClick() { + mHost.collapsePanels(); + Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + public void run() { + PowerManager pm = + (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + pm.reboot(mRebootToRecovery ? "recovery" : ""); + } + }, 500); + } + + @Override + public Intent getLongClickIntent() { + return null; + } + + @Override + public CharSequence getTileLabel() { + return mContext.getString(R.string.quick_settings_reboot_label); + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.RELOADED; + } + + @Override + protected void handleUpdateState(BooleanState state, Object arg) { + if (mRebootToRecovery) { + state.label = mContext.getString(R.string.quick_settings_reboot_recovery_label); + state.icon = ResourceIcon.get(R.drawable.ic_qs_reboot_recovery); + } else { + state.label = mContext.getString(R.string.quick_settings_reboot_label); + state.icon = ResourceIcon.get(R.drawable.ic_qs_reboot); + } + } + + @Override + public void handleSetListening(boolean listening) { + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 8b75d87ceb6..b1db6ba9874 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -875,7 +875,7 @@ public void onAnimationEnd(Animator animation) { @Override public void run() { if (Settings.System.getInt(mContext.getContentResolver(), - Settings.System.SCREENSHOT_SHUTTER_SOUND, 1) == 1) { + Settings.System.SCREENSHOT_SOUND, 1) == 1) { // Play the shutter sound to notify that we've taken a screenshot mCameraSound.play(MediaActionSound.SHUTTER_CLICK); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceCaptureErrorReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceCaptureErrorReceiver.java new file mode 100644 index 00000000000..b7feb73c988 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceCaptureErrorReceiver.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import android.app.NotificationManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.android.systemui.R; + +/** + * Performs a number of miscellaneous, non-system-critical actions + * after the system has finished booting. + */ +public class ScreenshotServiceCaptureErrorReceiver extends BroadcastReceiver { + + @Override + public void onReceive(final Context context, Intent intent) { + // Show a message that we've failed to save the image to disk + NotificationManager nm = (NotificationManager) + context.getSystemService(Context.NOTIFICATION_SERVICE); + GlobalScreenshot.notifyScreenshotError(context, nm, + R.string.screenshot_failed_to_capture_text); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 789bc926f75..7b6d3de1804 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -273,7 +273,7 @@ default void onRotationProposal(int rotation, boolean isValid) { } default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId) { } - default void onBiometricAuthenticated(boolean authenticated, String failureReason) { } + default void onBiometricAuthenticated(boolean authenticated, String failureReason, boolean requireConfirmation) { } default void onBiometricHelp(String message) { } default void onBiometricError(String error) { } default void hideBiometricDialog() { } @@ -755,11 +755,12 @@ public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal } @Override - public void onBiometricAuthenticated(boolean authenticated, String failureReason) { + public void onBiometricAuthenticated(boolean authenticated, String failureReason, boolean requireConfirmation) { synchronized (mLock) { SomeArgs args = SomeArgs.obtain(); args.arg1 = authenticated; args.arg2 = failureReason; + args.arg3 = requireConfirmation; mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, args).sendToTarget(); } } @@ -1046,7 +1047,8 @@ public void handleMessage(Message msg) { for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onBiometricAuthenticated( (boolean) someArgs.arg1 /* authenticated */, - (String) someArgs.arg2 /* failureReason */); + (String) someArgs.arg2 /* failureReason */, + (boolean) someArgs.arg3 /* requireConfirmation */); } someArgs.recycle(); break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 66a06193d0b..646281f5843 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -167,11 +167,12 @@ public void updateNotificationViews() { || !mLockscreenUserManager.needsSeparateWorkChallenge(userId))) { userPublic = false; } - boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent); + boolean appLocked = ent.getRow().isAppLocked(); + boolean needsRedaction = appLocked || mLockscreenUserManager.needsRedaction(ent); boolean sensitive = userPublic && needsRedaction; boolean deviceSensitive = devicePublic - && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic( - currentUserId); + && (appLocked || !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic( + currentUserId)); ent.setSensitive(sensitive, deviceSensitive); ent.getRow().setNeedsRedaction(needsRedaction); if (mGroupManager.isChildInGroupWithSummary(ent.notification)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index da0f83da5a7..a96f6d2672b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -19,8 +19,11 @@ import static android.service.notification.NotificationListenerService.REASON_ERROR; import android.annotation.Nullable; +import android.app.AppLockManager; +import android.app.AppLockManager.AppLockCallback; import android.app.Notification; import android.content.Context; +import android.os.Bundle; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; @@ -30,6 +33,7 @@ import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; +import com.android.systemui.R; import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -41,6 +45,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationRowBinder; import com.android.systemui.statusbar.notification.logging.NotificationLogger; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -98,6 +103,29 @@ public class NotificationEntryManager implements private final List mNotificationEntryListeners = new ArrayList<>(); private NotificationRemoveInterceptor mRemoveInterceptor; + private final AppLockManager mAppLockManager; + private final AppLockCallback mAppLockCallback = new AppLockCallback() { + @Override + public void onAppStateChanged(String pkg) { + updateAppNotifications(pkg); + } + }; + + private void updateAppNotifications(String pkg) { + ArrayList arr = mNotificationData.getAllNotificationsForPackage(pkg); + for (NotificationEntry notif : arr) { + if (notif.rowExists()) { + ExpandableNotificationRow row = notif.getRow(); + boolean appLocked = mAppLockManager.isAppLocked(pkg); + row.setAppLocked(appLocked); + Dependency.get(Dependency.MAIN_HANDLER).post(() -> { + row.onAppStateChanged(!mAppLockManager.getAppNotificationHide(pkg) + || mAppLockManager.isAppOpen(pkg)); + }); + } + } + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("NotificationEntryManager state:"); @@ -124,6 +152,8 @@ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { @Inject public NotificationEntryManager(Context context) { mNotificationData = new NotificationData(); + mAppLockManager = (AppLockManager) context.getSystemService(Context.APPLOCK_SERVICE); + mAppLockManager.addAppLockCallback(mAppLockCallback); } /** Adds a {@link NotificationEntryListener}. */ @@ -236,6 +266,16 @@ public void onAsyncInflationFinished(NotificationEntry entry, // If there was an async task started after the removal, we don't want to add it back to // the list, otherwise we might get leaks. if (!entry.isRowRemoved()) { + final String pkg = entry.notification.getPackageName(); + boolean isAppLocked = mAppLockManager.isAppLocked(pkg); + if (isAppLocked && entry.rowExists()) { + ExpandableNotificationRow row = entry.getRow(); + row.setAppLocked(isAppLocked); + Dependency.get(Dependency.MAIN_HANDLER).post(() -> { + row.onAppStateChanged(!mAppLockManager.getAppNotificationHide(pkg) || + mAppLockManager.isAppOpen(pkg)); + }); + } boolean isNew = mNotificationData.get(entry.key) == null; if (isNew) { for (NotificationEntryListener listener : mNotificationEntryListeners) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java index ef09434aa39..1c249bd0ba0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java @@ -164,6 +164,10 @@ public void setUpWithPresenter( * @return true if the entry should bubble up, false otherwise */ public boolean shouldBubbleUp(NotificationEntry entry) { + if (entry.rowExists() && entry.getRow().isAppLocked()) { + return false; + } + final StatusBarNotification sbn = entry.notification; if (!canAlertCommon(entry)) { @@ -216,6 +220,11 @@ public boolean shouldHeadsUp(NotificationEntry entry) { } private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) { + if (mStatusBarStateController.getState() != StatusBarState.KEYGUARD + && entry.rowExists() && entry.getRow().blockHeadsUp()) { + return false; + } + StatusBarNotification sbn = entry.notification; if (!mUseHeadsUp) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java index 7b4ed3f8a7d..f41ad08e8f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java @@ -185,6 +185,20 @@ public NotificationEntry get(String key) { return mEntries.get(key); } + public ArrayList getAllNotificationsForPackage(String pkg) { + synchronized (mEntries) { + final int len = mEntries.size(); + ArrayList filtered = new ArrayList<>(len); + for (int i = 0; i < len; i++) { + NotificationEntry entry = mEntries.valueAt(i); + if (pkg.equals(entry.notification.getPackageName())) { + filtered.add(entry); + } + } + return filtered; + } + } + public void add(NotificationEntry entry) { synchronized (mEntries) { mEntries.put(entry.notification.getKey(), entry); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java index 240d567b78c..04643d7c003 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java @@ -259,7 +259,7 @@ private void updateNotification( if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) { row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, true /* shouldInflate */); } - row.setNeedsRedaction( + row.setNeedsRedaction(row.isAppLocked() || Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry)); row.inflateViews(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 12d537d3c64..608c852b61c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -23,6 +23,7 @@ import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; +import android.app.AppLockManager; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -30,6 +31,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.NotificationChannel; +import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -327,6 +330,10 @@ public Float get(ExpandableNotificationRow object) { private SystemNotificationAsyncTask mSystemNotificationAsyncTask = new SystemNotificationAsyncTask(); + private boolean mAppOpen; + private boolean mIsAppLocked; + private boolean mIsAlarmOrCall; + /** * Returns whether the given {@code statusBarNotification} is a system notification. * Note, this should be run in the background thread if possible as it makes multiple IPC @@ -445,6 +452,19 @@ private void setIconRunning(ImageView imageView, boolean running) { public void setEntry(@NonNull NotificationEntry entry) { mEntry = entry; mStatusBarNotification = entry.notification; + if (mStatusBarNotification != null) { + updateAlarmOrCall(); + AppLockManager appLockManager = (AppLockManager) mContext + .getSystemService(Context.APPLOCK_SERVICE); + final String pkg = mStatusBarNotification.getPackageName(); + mIsAppLocked = appLockManager.isAppLocked(pkg); + if (mIsAppLocked) { + mAppOpen = appLockManager.isAppOpen(pkg) || !appLockManager + .getAppNotificationHide(pkg); + } + if (mIsAppLocked) Log.d(TAG, "setEntry() app:" + mAppName + + " mAppOpen:" + mAppOpen + " mIsAlarmOrCall:" + mIsAlarmOrCall); + } cacheIsSystemNotification(); } @@ -2318,7 +2338,7 @@ public int getIntrinsicHeight() { return mGuts.getIntrinsicHeight(); } else if ((isChildInGroup() && !isGroupExpanded())) { return mPrivateLayout.getMinHeight(); - } else if (mSensitive && mHideSensitiveForIntrinsicHeight) { + } else if (shouldShowPublic()) { return getMinHeight(); } else if (mIsSummaryWithChildren) { return mChildrenContainer.getIntrinsicHeight(); @@ -2342,7 +2362,7 @@ public int getIntrinsicHeight() { * except for legacy use cases. */ public boolean canShowHeadsUp() { - if (mOnKeyguard && !isDozing() && !isBypassEnabled()) { + if (mOnKeyguard && !isDozing() && !isBypassEnabled() || (mIsAppLocked && !mAppOpen)) { return false; } return true; @@ -2513,7 +2533,8 @@ public void setHideSensitive(boolean hideSensitive, boolean animated, long delay return; } boolean oldShowingPublic = mShowingPublic; - mShowingPublic = mSensitive && hideSensitive; + mShowingPublic = (mSensitive && hideSensitive) || (mIsAppLocked && !mAppOpen + && !mIsAlarmOrCall); if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) { return; } @@ -2574,6 +2595,51 @@ public void run() { } } + public void onAppStateChanged(boolean open) { + if (mAppOpen != open) { + mAppOpen = open; + setHideSensitive(mSensitive, true, 0, 100); + } + } + + public void setAppLocked(boolean locked) { + mIsAppLocked = locked; + } + + public boolean isAppLocked() { + return mIsAppLocked; + } + + public boolean blockHeadsUp() { + if (mIsAppLocked) Log.d(TAG, "blockHeadsUp() app:" + mAppName + + " mAppOpen:" + mAppOpen + " mIsAlarmOrCall:" + mIsAlarmOrCall); + return mIsAppLocked && !mAppOpen && !mIsAlarmOrCall; + } + + private void updateAlarmOrCall() { + PendingIntent intent = mStatusBarNotification.getNotification().contentIntent; + if (intent == null) { + mIsAlarmOrCall = false; + return; + } + + ComponentName cmp = intent.getIntent().getComponent(); + if (cmp != null) { + String intentClassName = cmp.getClassName().toLowerCase(); + mIsAlarmOrCall = intentClassName.contains("callactivity") + || intentClassName.contains("callingactivity") + || intentClassName.contains("voipactivity") + || intentClassName.contains("alarmactivity"); + } else { + intent = mStatusBarNotification.getNotification().fullScreenIntent; + if (intent != null) { + mIsAlarmOrCall = true; + } else { + mIsAlarmOrCall = false; + } + } + } + @Override public boolean mustStayOnScreen() { return mIsHeadsUp && mMustStayOnScreen; @@ -2589,7 +2655,8 @@ public boolean canViewBeDismissed() { } private boolean shouldShowPublic() { - return mSensitive && mHideSensitiveForIntrinsicHeight; + return (mSensitive && mHideSensitiveForIntrinsicHeight) || (mIsAppLocked && !mAppOpen + && !mIsAlarmOrCall); } public void makeActionsVisibile() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 41c6a7ba784..ac1b90d5d0c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -440,7 +440,7 @@ public int getMode() { private @WakeAndUnlockMode int calculateModeForPassiveAuth() { boolean unlockingAllowed = mUpdateMonitor.isUnlockingWithBiometricAllowed(); boolean deviceDreaming = mUpdateMonitor.isDreaming(); - boolean bypass = mKeyguardBypassController.getBypassEnabled(); + boolean bypass = mKeyguardBypassController.getBypassEnabledBiometric(); if (!mUpdateMonitor.isDeviceInteractive()) { if (!mStatusBarKeyguardViewManager.isShowing()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt index 832ea9e3d72..bcab3830c92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt @@ -47,6 +47,8 @@ class KeyguardBypassController { * If face unlock dismisses the lock screen or keeps user on keyguard for the current user. */ var bypassEnabled: Boolean = false + + var bypassEnabledBiometric: Boolean = false get() = field && unlockMethodCache.isFaceAuthEnabled private set @@ -88,7 +90,7 @@ class KeyguardBypassController { com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0 tunerService.addTunable(object : TunerService.Tunable { override fun onTuningChanged(key: String?, newValue: String?) { - bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0 + bypassEnabledBiometric = tunerService.getValue(key, dismissByDefault) != 0 } }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD) lockscreenUserManager.addUserChangedListener { pendingUnlockType = null } diff --git a/packages/SystemUI/src/com/google/android/systemui/NotificationLockscreenUserManagerGoogle.java b/packages/SystemUI/src/com/google/android/systemui/NotificationLockscreenUserManagerGoogle.java new file mode 100644 index 00000000000..8dc8ae6ab2b --- /dev/null +++ b/packages/SystemUI/src/com/google/android/systemui/NotificationLockscreenUserManagerGoogle.java @@ -0,0 +1,26 @@ +package com.google.android.systemui; + +import android.content.Context; +import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl; +import com.google.android.systemui.smartspace.SmartSpaceController; + +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton +public class NotificationLockscreenUserManagerGoogle extends NotificationLockscreenUserManagerImpl { + @Inject + public NotificationLockscreenUserManagerGoogle(Context context) { + super(context); + } + + @Override + public void updateLockscreenNotificationSetting() { + super.updateLockscreenNotificationSetting(); + updateAodVisibilitySettings(); + } + + public void updateAodVisibilitySettings() { + SmartSpaceController.get(this.mContext).setHideSensitiveData(!userAllowsPrivateNotificationsInPublic(this.mCurrentUserId)); + } +} diff --git a/packages/SystemUI/src/com/google/android/systemui/SystemUIGoogleModule.java b/packages/SystemUI/src/com/google/android/systemui/SystemUIGoogleModule.java new file mode 100644 index 00000000000..cb556e26d02 --- /dev/null +++ b/packages/SystemUI/src/com/google/android/systemui/SystemUIGoogleModule.java @@ -0,0 +1,19 @@ +package com.google.android.systemui; + +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.notification.NotificationEntryManager; + +import dagger.Binds; +import dagger.Module; + +/** + * A dagger module for injecting default implementations of components of System UI that may be + * overridden by the System UI implementation. + */ +@Module +public abstract class SystemUIGoogleModule { + + @Binds + abstract NotificationLockscreenUserManager bindNotificationLockscreenUserManager( + NotificationLockscreenUserManagerGoogle notificationLockscreenUserManager); +} diff --git a/packages/SystemUI/src/com/google/android/systemui/keyguard/KeyguardSliceProviderGoogle.java b/packages/SystemUI/src/com/google/android/systemui/keyguard/KeyguardSliceProviderGoogle.java new file mode 100644 index 00000000000..4d343c3c814 --- /dev/null +++ b/packages/SystemUI/src/com/google/android/systemui/keyguard/KeyguardSliceProviderGoogle.java @@ -0,0 +1,184 @@ +package com.google.android.systemui.keyguard; + +import android.app.PendingIntent; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.BlurMaskFilter; +import android.graphics.BlurMaskFilter.Blur; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff.Mode; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Trace; +import android.text.TextUtils; +import android.util.Log; +import androidx.core.graphics.drawable.IconCompat; +import androidx.slice.Slice; +import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.ListBuilder.HeaderBuilder; +import androidx.slice.builders.ListBuilder.RowBuilder; +import androidx.slice.builders.SliceAction; +import com.android.systemui.R; +import com.android.systemui.keyguard.KeyguardSliceProvider; +import com.google.android.systemui.smartspace.SmartSpaceCard; +import com.google.android.systemui.smartspace.SmartSpaceController; +import com.google.android.systemui.smartspace.SmartSpaceData; +import com.google.android.systemui.smartspace.SmartSpaceUpdateListener; +import java.lang.ref.WeakReference; + +public class KeyguardSliceProviderGoogle extends KeyguardSliceProvider implements SmartSpaceUpdateListener { + private final Uri mCalendarUri = Uri.parse("content://com.android.systemui.keyguard/smartSpace/calendar"); + private SmartSpaceData mSmartSpaceData; + private final Uri mWeatherUri = Uri.parse("content://com.android.systemui.keyguard/smartSpace/weather"); + + private static class AddShadowTask extends AsyncTask { + private final float mBlurRadius; + private final WeakReference mProviderReference; + private final SmartSpaceCard mWeatherCard; + + AddShadowTask(KeyguardSliceProviderGoogle keyguardSliceProviderGoogle, SmartSpaceCard smartSpaceCard) { + mProviderReference = new WeakReference<>(keyguardSliceProviderGoogle); + mWeatherCard = smartSpaceCard; + mBlurRadius = keyguardSliceProviderGoogle.getContext().getResources().getDimension(R.dimen.smartspace_icon_shadow); + } + + @Override + public Bitmap doInBackground(Bitmap... bitmapArr) { + return applyShadow(bitmapArr[0]); + } + + @Override + public void onPostExecute(Bitmap bitmap) { + KeyguardSliceProviderGoogle keyguardSliceProviderGoogle; + synchronized (this) { + mWeatherCard.setIcon(bitmap); + keyguardSliceProviderGoogle = (KeyguardSliceProviderGoogle) mProviderReference.get(); + } + if (keyguardSliceProviderGoogle != null) { + keyguardSliceProviderGoogle.notifyChange(); + } + } + + private Bitmap applyShadow(Bitmap bitmap) { + BlurMaskFilter blurMaskFilter = new BlurMaskFilter(mBlurRadius, Blur.NORMAL); + Paint paint = new Paint(); + paint.setMaskFilter(blurMaskFilter); + int[] iArr = new int[2]; + Bitmap extractAlpha = bitmap.extractAlpha(paint, iArr); + Bitmap createBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888); + Canvas canvas = new Canvas(createBitmap); + Paint paint2 = new Paint(); + paint2.setAlpha(70); + canvas.drawBitmap(extractAlpha, (float) iArr[0], ((float) iArr[1]) + (mBlurRadius / 2.0f), paint2); + extractAlpha.recycle(); + paint2.setAlpha(255); + canvas.drawBitmap(bitmap, 0.0f, 0.0f, paint2); + return createBitmap; + } + } + + @Override + public boolean onCreateSliceProvider() { + boolean onCreateSliceProvider = super.onCreateSliceProvider(); + SmartSpaceController.get(getContext()).addListener(this); + mSmartSpaceData = new SmartSpaceData(); + return onCreateSliceProvider; + } + + @Override + public Slice onBindSlice(Uri uri) { + Trace.beginSection("KeyguardSliceProviderGoogle#onBindSlice"); + Slice slice; + synchronized (this) { + ListBuilder listBuilder = new ListBuilder(getContext(), mSliceUri, -1); + SmartSpaceCard currentCard = mSmartSpaceData.getCurrentCard(); + if (currentCard == null || currentCard.isExpired() || TextUtils.isEmpty(currentCard.getTitle())) { + if (needsMediaLocked()) { + addMediaLocked(listBuilder); + } else { + RowBuilder rowBuilder = new RowBuilder(mDateUri); + rowBuilder.setTitle(getFormattedDateLocked()); + listBuilder.addRow(rowBuilder); + } + addWeather(listBuilder); + addNextAlarmLocked(listBuilder); + addZenModeLocked(listBuilder); + addPrimaryActionLocked(listBuilder); + } else { + Bitmap icon = currentCard.getIcon(); + SliceAction sliceAction = null; + IconCompat iconCompat; + if (icon == null) { + iconCompat = null; + } else { + iconCompat = IconCompat.createWithBitmap(icon); + } + PendingIntent pendingIntent = currentCard.getPendingIntent(); + if (iconCompat != null) { + if (pendingIntent != null) { + sliceAction = SliceAction.create(pendingIntent, iconCompat, 1, currentCard.getTitle()); + } + } + HeaderBuilder headerBuilder = new HeaderBuilder(mHeaderUri); + headerBuilder.setTitle(currentCard.getFormattedTitle()); + if (sliceAction != null) { + headerBuilder.setPrimaryAction(sliceAction); + } + listBuilder.setHeader(headerBuilder); + String subtitle = currentCard.getSubtitle(); + if (subtitle != null) { + RowBuilder rowBuilder2 = new RowBuilder(mCalendarUri); + rowBuilder2.setTitle(subtitle); + if (iconCompat != null) { + rowBuilder2.addEndItem(iconCompat, 1); + } + if (sliceAction != null) { + rowBuilder2.setPrimaryAction(sliceAction); + } + listBuilder.addRow(rowBuilder2); + } + addWeather(listBuilder); + addZenModeLocked(listBuilder); + addPrimaryActionLocked(listBuilder); + } + slice = listBuilder.build(); + } + Trace.endSection(); + return slice; + } + + private void addWeather(ListBuilder listBuilder) { + SmartSpaceCard weatherCard = mSmartSpaceData.getWeatherCard(); + if (weatherCard != null && !weatherCard.isExpired()) { + RowBuilder rowBuilder = new RowBuilder(mWeatherUri); + rowBuilder.setTitle(weatherCard.getTitle()); + Bitmap icon = weatherCard.getIcon(); + if (icon != null) { + IconCompat createWithBitmap = IconCompat.createWithBitmap(icon); + createWithBitmap.setTintMode(Mode.DST); + rowBuilder.addEndItem(createWithBitmap, 1); + } + listBuilder.addRow(rowBuilder); + } + } + + @Override + public void onSmartSpaceUpdated(SmartSpaceData smartSpaceData) { + synchronized (this) { + mSmartSpaceData = smartSpaceData; + } + SmartSpaceCard weatherCard = smartSpaceData.getWeatherCard(); + if (weatherCard == null || weatherCard.getIcon() == null || weatherCard.isIconProcessed()) { + notifyChange(); + return; + } + weatherCard.setIconProcessed(true); + new AddShadowTask(this, weatherCard).execute(new Bitmap[]{weatherCard.getIcon()}); + } + + @Override + public void updateClockLocked() { + notifyChange(); + } +} diff --git a/packages/SystemUI/src/com/google/android/systemui/smartspace/GSAIntents.java b/packages/SystemUI/src/com/google/android/systemui/smartspace/GSAIntents.java new file mode 100644 index 00000000000..f8dc0fd250e --- /dev/null +++ b/packages/SystemUI/src/com/google/android/systemui/smartspace/GSAIntents.java @@ -0,0 +1,19 @@ +package com.google.android.systemui.smartspace; + +import android.content.IntentFilter; + +public class GSAIntents { + public static IntentFilter getGsaPackageFilter(String... strArr) { + return getPackageFilter("com.google.android.googlequicksearchbox", strArr); + } + + public static IntentFilter getPackageFilter(String str, String... strArr) { + IntentFilter intentFilter = new IntentFilter(); + for (String addAction : strArr) { + intentFilter.addAction(addAction); + } + intentFilter.addDataScheme("package"); + intentFilter.addDataSchemeSpecificPart(str, 0); + return intentFilter; + } +} diff --git a/packages/SystemUI/src/com/google/android/systemui/smartspace/NewCardInfo.java b/packages/SystemUI/src/com/google/android/systemui/smartspace/NewCardInfo.java new file mode 100644 index 00000000000..40b75db2d64 --- /dev/null +++ b/packages/SystemUI/src/com/google/android/systemui/smartspace/NewCardInfo.java @@ -0,0 +1,114 @@ +package com.google.android.systemui.smartspace; + +import android.content.Context; +import android.content.Intent; +import android.content.Intent.ShortcutIconResource; +import android.content.pm.PackageInfo; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.provider.MediaStore.Images.Media; +import android.text.TextUtils; +import android.util.Log; +import com.google.android.systemui.smartspace.nano.SmartspaceProto.CardWrapper; +import com.google.android.systemui.smartspace.nano.SmartspaceProto.SmartspaceUpdate.SmartspaceCard; +import com.google.android.systemui.smartspace.nano.SmartspaceProto.SmartspaceUpdate.SmartspaceCard.Image; +import com.google.android.systemui.smartspace.nano.SmartspaceProto.SmartspaceUpdate.SmartspaceCard.Image; + +import java.io.ByteArrayOutputStream; + +public class NewCardInfo { + private final SmartspaceCard mCard; + private final Intent mIntent; + private final boolean mIsPrimary; + private final PackageInfo mPackageInfo; + private final long mPublishTime; + + public NewCardInfo(SmartspaceCard smartspaceCard, Intent intent, boolean z, long j, PackageInfo packageInfo) { + mCard = smartspaceCard; + mIsPrimary = z; + mIntent = intent; + mPublishTime = j; + mPackageInfo = packageInfo; + } + + public boolean isPrimary() { + return mIsPrimary; + } + + public Bitmap retrieveIcon(Context context) { + Image image = mCard.icon; + if (image == null) { + return null; + } + Bitmap bitmap = (Bitmap) retrieveFromIntent(image.key, mIntent); + if (bitmap != null) { + return bitmap; + } + try { + if (!TextUtils.isEmpty(image.uri)) { + return Media.getBitmap(context.getContentResolver(), Uri.parse(image.uri)); + } + if (!TextUtils.isEmpty(image.gsaResourceName)) { + ShortcutIconResource shortcutIconResource = new ShortcutIconResource(); + shortcutIconResource.packageName = "com.google.android.googlequicksearchbox"; + shortcutIconResource.resourceName = image.gsaResourceName; + return createIconBitmap(shortcutIconResource, context); + } + } catch (Exception unused) { + StringBuilder sb = new StringBuilder(); + sb.append("retrieving bitmap uri="); + sb.append(image.uri); + sb.append(" gsaRes="); + sb.append(image.gsaResourceName); + Log.e("NewCardInfo", sb.toString()); + } + return null; + } + + public CardWrapper toWrapper(Context context) { + CardWrapper cardWrapper = new CardWrapper(); + Bitmap retrieveIcon = retrieveIcon(context); + if (retrieveIcon != null) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + retrieveIcon.compress(CompressFormat.PNG, 100, byteArrayOutputStream); + cardWrapper.icon = byteArrayOutputStream.toByteArray(); + } + cardWrapper.card = mCard; + cardWrapper.publishTime = mPublishTime; + PackageInfo packageInfo = mPackageInfo; + if (packageInfo != null) { + cardWrapper.gsaVersionCode = packageInfo.versionCode; + cardWrapper.gsaUpdateTime = packageInfo.lastUpdateTime; + } + return cardWrapper; + } + + private static T retrieveFromIntent(String str, Intent intent) { + if (!TextUtils.isEmpty(str)) { + return intent.getParcelableExtra(str); + } + return null; + } + + static Bitmap createIconBitmap(ShortcutIconResource shortcutIconResource, Context context) { + try { + Resources resourcesForApplication = context.getPackageManager().getResourcesForApplication(shortcutIconResource.packageName); + if (resourcesForApplication != null) { + return BitmapFactory.decodeResource(resourcesForApplication, resourcesForApplication.getIdentifier(shortcutIconResource.resourceName, null, null)); + } + } catch (Exception ignored) { + } + return null; + } + + public int getUserId() { + return mIntent.getIntExtra("uid", -1); + } + + public boolean shouldDiscard() { + return mCard == null || mCard.shouldDiscard; + } +} diff --git a/packages/SystemUI/src/com/google/android/systemui/smartspace/ProtoStore.java b/packages/SystemUI/src/com/google/android/systemui/smartspace/ProtoStore.java new file mode 100644 index 00000000000..9874f8b4ff7 --- /dev/null +++ b/packages/SystemUI/src/com/google/android/systemui/smartspace/ProtoStore.java @@ -0,0 +1,69 @@ +package com.google.android.systemui.smartspace; + +import android.content.Context; +import android.util.Log; +import com.google.protobuf.nano.MessageNano; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; + +public class ProtoStore { + private final Context mContext; + + public ProtoStore(Context context) { + mContext = context.getApplicationContext(); + } + + public void store(MessageNano messageNano, String str) { + String str2 = "ProtoStore"; + try { + FileOutputStream openFileOutput = mContext.openFileOutput(str, 0); + if (messageNano != null) { + try { + openFileOutput.write(MessageNano.toByteArray(messageNano)); + } catch (Throwable th) { + if (openFileOutput != null) { + openFileOutput.close(); + } + throw th; + } + } else { + StringBuilder sb = new StringBuilder(); + sb.append("deleting "); + sb.append(str); + Log.d(str2, sb.toString()); + mContext.deleteFile(str); + } + if (openFileOutput != null) { + openFileOutput.close(); + } + } catch (FileNotFoundException unused) { + Log.d(str2, "file does not exist"); + } catch (Exception e) { + Log.e(str2, "unable to write file", e); + } + } + + public boolean load(String str, T t) { + String str2 = "ProtoStore"; + File fileStreamPath = mContext.getFileStreamPath(str); + try { + FileInputStream fileInputStream = new FileInputStream(fileStreamPath); + byte[] bArr = new byte[((int) fileStreamPath.length())]; + fileInputStream.read(bArr, 0, bArr.length); + MessageNano.mergeFrom(t, bArr); + if (fileInputStream != null) { + fileInputStream.close(); + } + return true; + } catch (FileNotFoundException unused) { + Log.d(str2, "no cached data"); + return false; + } catch (Exception e) { + Log.e(str2, "unable to load data", e); + return false; + } + } +} diff --git a/packages/SystemUI/src/com/google/android/systemui/smartspace/SmartSpaceBroadcastReceiver.java b/packages/SystemUI/src/com/google/android/systemui/smartspace/SmartSpaceBroadcastReceiver.java new file mode 100644 index 00000000000..7ce4c7d388f --- /dev/null +++ b/packages/SystemUI/src/com/google/android/systemui/smartspace/SmartSpaceBroadcastReceiver.java @@ -0,0 +1,85 @@ +package com.google.android.systemui.smartspace; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.UserHandle; +import android.util.Log; +import com.google.android.systemui.smartspace.nano.SmartspaceProto.SmartspaceUpdate; +import com.google.android.systemui.smartspace.nano.SmartspaceProto.SmartspaceUpdate.SmartspaceCard; +import com.google.protobuf.nano.InvalidProtocolBufferNanoException; +import com.google.protobuf.nano.MessageNano; + +public class SmartSpaceBroadcastReceiver extends BroadcastReceiver { + private final SmartSpaceController mController; + + public SmartSpaceBroadcastReceiver(SmartSpaceController smartSpaceController) { + mController = smartSpaceController; + } + + @Override + public void onReceive(Context context, Intent intent) { + SmartspaceCard[] smartspaceCardArr; + String str = "SmartSpaceReceiver"; + if (SmartSpaceController.DEBUG) { + Log.d(str, "receiving update"); + } + int myUserId = UserHandle.myUserId(); + String str2 = "uid"; + if (myUserId != 0) { + String str3 = "rebroadcast"; + if (!intent.getBooleanExtra(str3, false)) { + intent.putExtra(str3, true); + intent.putExtra(str2, myUserId); + context.sendBroadcastAsUser(intent, UserHandle.ALL); + return; + } + return; + } + if (!intent.hasExtra(str2)) { + intent.putExtra(str2, myUserId); + } + byte[] byteArrayExtra = intent.getByteArrayExtra("com.google.android.apps.nexuslauncher.extra.SMARTSPACE_CARD"); + if (byteArrayExtra != null) { + SmartspaceUpdate smartspaceUpdate = new SmartspaceUpdate(); + try { + MessageNano.mergeFrom(smartspaceUpdate, byteArrayExtra); + for (SmartspaceCard smartspaceCard : smartspaceUpdate.card) { + boolean z = smartspaceCard.cardPriority == 1; + boolean z2 = smartspaceCard.cardPriority == 2; + if (!z) { + if (!z2) { + StringBuilder sb = new StringBuilder(); + sb.append("unrecognized card priority: "); + sb.append(smartspaceCard.cardPriority); + Log.w(str, sb.toString()); + } + } + notify(smartspaceCard, context, intent, z); + } + } catch (InvalidProtocolBufferNanoException e) { + Log.e(str, "proto", e); + } + } else { + StringBuilder sb2 = new StringBuilder(); + sb2.append("receiving update with no proto: "); + sb2.append(intent.getExtras()); + Log.e(str, sb2.toString()); + } + } + + private void notify(SmartspaceCard smartspaceCard, Context context, Intent intent, boolean z) { + PackageInfo packageInfo; + long currentTimeMillis = System.currentTimeMillis(); + try { + packageInfo = context.getPackageManager().getPackageInfo("com.google.android.googlequicksearchbox", 0); + } catch (NameNotFoundException e) { + Log.w("SmartSpaceReceiver", "Cannot find GSA", e); + packageInfo = null; + } + NewCardInfo newCardInfo = new NewCardInfo(smartspaceCard, intent, z, currentTimeMillis, packageInfo); + mController.onNewCard(newCardInfo); + } +} diff --git a/packages/SystemUI/src/com/google/android/systemui/smartspace/SmartSpaceCard.java b/packages/SystemUI/src/com/google/android/systemui/smartspace/SmartSpaceCard.java new file mode 100644 index 00000000000..c8cd4f80ebc --- /dev/null +++ b/packages/SystemUI/src/com/google/android/systemui/smartspace/SmartSpaceCard.java @@ -0,0 +1,329 @@ +package com.google.android.systemui.smartspace; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import com.android.systemui.R; +import com.google.android.systemui.smartspace.nano.SmartspaceProto.CardWrapper; +import com.google.android.systemui.smartspace.nano.SmartspaceProto.SmartspaceUpdate.SmartspaceCard; +import com.google.android.systemui.smartspace.nano.SmartspaceProto.SmartspaceUpdate.SmartspaceCard.ExpiryCriteria; +import com.google.android.systemui.smartspace.nano.SmartspaceProto.SmartspaceUpdate.SmartspaceCard.Message; +import com.google.android.systemui.smartspace.nano.SmartspaceProto.SmartspaceUpdate.SmartspaceCard.Message.FormattedText; +import com.google.android.systemui.smartspace.nano.SmartspaceProto.SmartspaceUpdate.SmartspaceCard.Message.FormattedText.FormatParam; + +public class SmartSpaceCard { + private static int sRequestCode; + private final SmartspaceCard mCard; + private final Context mContext; + private Bitmap mIcon; + private boolean mIconProcessed; + private final Intent mIntent; + private final boolean mIsIconGrayscale; + private final boolean mIsWeather; + private final long mPublishTime; + private int mRequestCode; + + public SmartSpaceCard(Context context, SmartspaceCard smartspaceCard, Intent intent, boolean z, Bitmap bitmap, boolean z2, long j) { + mContext = context.getApplicationContext(); + mCard = smartspaceCard; + mIsWeather = z; + mIntent = intent; + mIcon = bitmap; + mPublishTime = j; + mIsIconGrayscale = z2; + int i = sRequestCode + 1; + sRequestCode = i; + if (i > 2147483646) { + sRequestCode = 0; + } + mRequestCode = sRequestCode; + } + + public Intent getIntent() { + return mIntent; + } + + public Bitmap getIcon() { + return mIcon; + } + + public void setIcon(Bitmap bitmap) { + mIcon = bitmap; + } + + public void setIconProcessed(boolean z) { + mIconProcessed = z; + } + + public boolean isIconProcessed() { + return mIconProcessed; + } + + public String getTitle() { + return substitute(true); + } + + public CharSequence getFormattedTitle() { + FormatParam[] formatParamArr; + Message message = getMessage(); + String str = ""; + if (message == null) { + return str; + } + FormattedText formattedText = message.title; + if (formattedText != null) { + String str2 = formattedText.text; + if (str2 != null) { + if (!hasParams(formattedText)) { + return str2; + } + String str3 = null; + String str4 = null; + int i = 0; + while (true) { + formatParamArr = formattedText.formatParam; + if (i >= formatParamArr.length) { + break; + } + FormatParam formatParam = formatParamArr[i]; + if (formatParam != null) { + int i2 = formatParam.formatParamArgs; + if (i2 == 1 || i2 == 2) { + str4 = getDurationText(formatParam); + } else if (i2 == 3) { + str3 = formatParam.text; + } + } + i++; + } + if (mCard.cardType == 3 && formatParamArr.length == 2) { + str4 = formatParamArr[0].text; + str3 = formatParamArr[1].text; + } + if (str3 == null) { + return str; + } + if (str4 == null) { + if (message != mCard.duringEvent) { + return str2; + } + str4 = mContext.getString(R.string.smartspace_now); + } + return mContext.getString(R.string.smartspace_pill_text_format, new Object[]{str4, str3}); + } + } + return str; + } + + public String getSubtitle() { + return substitute(false); + } + + private Message getMessage() { + long currentTimeMillis = System.currentTimeMillis(); + SmartspaceCard smartspaceCard = mCard; + long j = smartspaceCard.eventTimeMillis; + long j2 = smartspaceCard.eventDurationMillis + j; + if (currentTimeMillis < j) { + Message message = smartspaceCard.preEvent; + if (message != null) { + return message; + } + } + if (currentTimeMillis > j2) { + Message message2 = mCard.postEvent; + if (message2 != null) { + return message2; + } + } + Message message3 = mCard.duringEvent; + if (message3 != null) { + return message3; + } + return null; + } + + private FormattedText getFormattedText(boolean z) { + Message message = getMessage(); + if (message == null) { + return null; + } + return z ? message.title : message.subtitle; + } + + private boolean hasParams(FormattedText formattedText) { + if (!(formattedText == null || formattedText.text == null)) { + FormatParam[] formatParamArr = formattedText.formatParam; + if (formatParamArr != null && formatParamArr.length > 0) { + return true; + } + } + return false; + } + + public long getMillisToEvent(FormatParam formatParam) { + long j; + if (formatParam.formatParamArgs == 2) { + SmartspaceCard smartspaceCard = mCard; + j = smartspaceCard.eventTimeMillis + smartspaceCard.eventDurationMillis; + } else { + j = mCard.eventTimeMillis; + } + return Math.abs(System.currentTimeMillis() - j); + } + + private int getMinutesToEvent(FormatParam formatParam) { + return (int) Math.ceil(((double) getMillisToEvent(formatParam)) / 60000.0d); + } + + private String substitute(boolean z) { + return substitute(z, null); + } + + private String[] getTextArgs(FormatParam[] formatParamArr, String str) { + String[] strArr = new String[formatParamArr.length]; + for (int i = 0; i < strArr.length; i++) { + int i2 = formatParamArr[i].formatParamArgs; + if (i2 == 1 || i2 == 2) { + strArr[i] = getDurationText(formatParamArr[i]); + } else { + String str2 = ""; + if (i2 != 3) { + strArr[i] = str2; + } else if (str == null || formatParamArr[i].truncateLocation == 0) { + if (formatParamArr[i].text != null) { + str2 = formatParamArr[i].text; + } + strArr[i] = str2; + } else { + strArr[i] = str; + } + } + } + return strArr; + } + + private String getDurationText(FormatParam formatParam) { + int minutesToEvent = getMinutesToEvent(formatParam); + if (minutesToEvent >= 60) { + int i = minutesToEvent / 60; + int i2 = minutesToEvent % 60; + String quantityString = mContext.getResources().getQuantityString(R.plurals.smartspace_hours, i, new Object[]{Integer.valueOf(i)}); + if (i2 <= 0) { + return quantityString; + } + String quantityString2 = mContext.getResources().getQuantityString(R.plurals.smartspace_minutes, i2, new Object[]{Integer.valueOf(i2)}); + return mContext.getString(R.string.smartspace_hours_mins, new Object[]{quantityString, quantityString2}); + } + return mContext.getResources().getQuantityString(R.plurals.smartspace_minutes, minutesToEvent, new Object[]{Integer.valueOf(minutesToEvent)}); + } + + private String substitute(boolean z, String str) { + FormattedText formattedText = getFormattedText(z); + if (formattedText != null) { + String str2 = formattedText.text; + if (str2 != null) { + return hasParams(formattedText) ? String.format(str2, (Object[]) getTextArgs(formattedText.formatParam, str)) : str2; + } + } + return ""; + } + + public boolean isExpired() { + return System.currentTimeMillis() > getExpiration(); + } + + public long getExpiration() { + SmartspaceCard smartspaceCard = mCard; + if (smartspaceCard != null) { + ExpiryCriteria expiryCriteria = smartspaceCard.expiryCriteria; + if (expiryCriteria != null) { + return expiryCriteria.expirationTimeMillis; + } + } + return 0; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("title:"); + sb.append(getTitle()); + sb.append(" subtitle:"); + sb.append(getSubtitle()); + sb.append(" expires:"); + sb.append(getExpiration()); + sb.append(" published:"); + sb.append(mPublishTime); + return sb.toString(); + } + + static SmartSpaceCard fromWrapper(Context context, CardWrapper cardWrapper, boolean z) { + if (cardWrapper == null) { + return null; + } + try { + Intent parseUri = (cardWrapper.card.tapAction == null || TextUtils.isEmpty(cardWrapper.card.tapAction.intent)) ? null : Intent.parseUri(cardWrapper.card.tapAction.intent, 0); + Bitmap decodeByteArray = cardWrapper.icon != null ? BitmapFactory.decodeByteArray(cardWrapper.icon, 0, cardWrapper.icon.length, null) : null; + int dimensionPixelSize = context.getResources().getDimensionPixelSize(R.dimen.header_icon_size); + if (decodeByteArray != null && decodeByteArray.getHeight() > dimensionPixelSize) { + decodeByteArray = Bitmap.createScaledBitmap(decodeByteArray, (int) (((float) decodeByteArray.getWidth()) * (((float) dimensionPixelSize) / ((float) decodeByteArray.getHeight()))), dimensionPixelSize, true); + } + SmartSpaceCard smartSpaceCard = new SmartSpaceCard(context, cardWrapper.card, parseUri, z, decodeByteArray, cardWrapper.isIconGrayscale, cardWrapper.publishTime); + return smartSpaceCard; + } catch (Exception e) { + Log.e("SmartspaceCard", "from proto", e); + return null; + } + } + + public void performCardAction(View view) { + String str = "SmartspaceCard"; + if (mCard.tapAction == null) { + StringBuilder sb = new StringBuilder(); + sb.append("no tap action available: "); + sb.append(this); + Log.e(str, sb.toString()); + return; + } + Intent intent = new Intent(getIntent()); + int i = mCard.tapAction.actionType; + if (i == 1) { + intent.addFlags(268435456); + intent.setPackage("com.google.android.googlequicksearchbox"); + try { + view.getContext().sendBroadcast(intent); + } catch (SecurityException e) { + Log.w(str, "Cannot perform click action", e); + } + } else if (i != 2) { + StringBuilder sb2 = new StringBuilder(); + sb2.append("unrecognized tap action: "); + sb2.append(mCard.tapAction.actionType); + Log.w(str, sb2.toString()); + } else { + mContext.startActivity(intent); + } + } + + public PendingIntent getPendingIntent() { + if (mCard.tapAction == null) { + return null; + } + Intent intent = new Intent(getIntent()); + int i = mCard.tapAction.actionType; + if (i == 1) { + intent.addFlags(268435456); + intent.setPackage("com.google.android.googlequicksearchbox"); + return PendingIntent.getBroadcast(mContext, mRequestCode, intent, 0); + } else if (i != 2) { + return null; + } else { + return PendingIntent.getActivity(mContext, mRequestCode, intent, 0); + } + } +} diff --git a/packages/SystemUI/src/com/google/android/systemui/smartspace/SmartSpaceController.java b/packages/SystemUI/src/com/google/android/systemui/smartspace/SmartSpaceController.java new file mode 100644 index 00000000000..a349dbec2e7 --- /dev/null +++ b/packages/SystemUI/src/com/google/android/systemui/smartspace/SmartSpaceController.java @@ -0,0 +1,340 @@ +package com.google.android.systemui.smartspace; + +import android.app.AlarmManager; +import android.app.AlarmManager.OnAlarmListener; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings.Global; +import android.util.KeyValueListParser; +import android.util.Log; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.systemui.Dumpable; +import com.google.android.systemui.smartspace.nano.SmartspaceProto.CardWrapper; +import com.android.systemui.util.Assert; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; + +public class SmartSpaceController implements Dumpable { + static final boolean DEBUG = Log.isLoggable("SmartSpaceController", 3); + private static SmartSpaceController sInstance; + private final AlarmManager mAlarmManager; + private boolean mAlarmRegistered; + private final Context mAppContext; + private final Handler mBackgroundHandler; + private final Context mContext; + public int mCurrentUserId; + public final SmartSpaceData mData; + private final OnAlarmListener mExpireAlarmAction = new OnAlarmListener() { + @Override + public final void onAlarm() { + onExpire(false); + } + }; + private boolean mHidePrivateData; + private final KeyguardUpdateMonitorCallback mKeyguardMonitorCallback = new KeyguardUpdateMonitorCallback() { + public void onTimeChanged() { + if (mData != null && mData.hasCurrent() && mData.getExpirationRemainingMillis() > 0) { + update(); + } + } + }; + private final ArrayList mListeners = new ArrayList<>(); + private boolean mSmartSpaceEnabledBroadcastSent; + private final ProtoStore mStore; + private final Handler mUiHandler; + + private class UserSwitchReceiver extends BroadcastReceiver { + private UserSwitchReceiver() { + } + + public void onReceive(Context context, Intent intent) { + if (SmartSpaceController.DEBUG) { + StringBuilder sb = new StringBuilder(); + sb.append("Switching user: "); + sb.append(intent.getAction()); + sb.append(" uid: "); + sb.append(UserHandle.myUserId()); + Log.d("SmartSpaceController", sb.toString()); + } + if (intent.getAction().equals("android.intent.action.USER_SWITCHED")) { + mCurrentUserId = intent.getIntExtra("android.intent.extra.user_handle", -1); + mData.clear(); + onExpire(true); + } + onExpire(true); + } + } + + public static SmartSpaceController get(Context context) { + if (sInstance == null) { + if (DEBUG) { + Log.d("SmartSpaceController", "controller created"); + } + sInstance = new SmartSpaceController(context.getApplicationContext()); + } + return sInstance; + } + + private SmartSpaceController(Context context) { + mContext = context; + mUiHandler = new Handler(Looper.getMainLooper()); + mStore = new ProtoStore(mContext); + HandlerThread handlerThread = new HandlerThread("smartspace-background"); + handlerThread.start(); + mBackgroundHandler = new Handler(handlerThread.getLooper()); + mCurrentUserId = UserHandle.myUserId(); + mAppContext = context; + mAlarmManager = (AlarmManager) context.getSystemService(AlarmManager.class); + mData = new SmartSpaceData(); + if (!isSmartSpaceDisabledByExperiments()) { + KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mKeyguardMonitorCallback); + reloadData(); + onGsaChanged(); + context.registerReceiver(new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + onGsaChanged(); + } + }, GSAIntents.getGsaPackageFilter("android.intent.action.PACKAGE_ADDED", "android.intent.action.PACKAGE_CHANGED", "android.intent.action.PACKAGE_REMOVED", "android.intent.action.PACKAGE_DATA_CLEARED")); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction("android.intent.action.USER_SWITCHED"); + intentFilter.addAction("android.intent.action.USER_UNLOCKED"); + context.registerReceiver(new UserSwitchReceiver(), intentFilter); + context.registerReceiver(new SmartSpaceBroadcastReceiver(this), new IntentFilter("com.google.android.apps.nexuslauncher.UPDATE_SMARTSPACE")); + } + } + + private SmartSpaceCard loadSmartSpaceData(boolean z) { + CardWrapper cardWrapper = new CardWrapper(); + ProtoStore protoStore = mStore; + StringBuilder sb = new StringBuilder(); + sb.append("smartspace_"); + sb.append(mCurrentUserId); + sb.append("_"); + sb.append(z); + if (protoStore.load(sb.toString(), cardWrapper)) { + return SmartSpaceCard.fromWrapper(mContext, cardWrapper, !z); + } + return null; + } + + public void onNewCard(final NewCardInfo newCardInfo) { + String str = "SmartSpaceController"; + if (DEBUG) { + StringBuilder sb = new StringBuilder(); + sb.append("onNewCard: "); + sb.append(newCardInfo); + Log.d(str, sb.toString()); + } + if (newCardInfo != null) { + if (newCardInfo.getUserId() != mCurrentUserId) { + if (DEBUG) { + StringBuilder sb2 = new StringBuilder(); + sb2.append("Ignore card that belongs to another user target: "); + sb2.append(mCurrentUserId); + sb2.append(" current: "); + sb2.append(mCurrentUserId); + Log.d(str, sb2.toString()); + } + return; + } + mBackgroundHandler.post(new Runnable() { + @Override + public final void run() { + final CardWrapper wrapper = newCardInfo.toWrapper(mContext); + if (!mHidePrivateData) { + ProtoStore protoStore = mStore; + StringBuilder sb = new StringBuilder(); + sb.append("smartspace_"); + sb.append(mCurrentUserId); + sb.append("_"); + sb.append(newCardInfo.isPrimary()); + protoStore.store(wrapper, sb.toString()); + } + mUiHandler.post(new Runnable() { + @Override + public final void run() { + SmartSpaceCard smartSpaceCard = newCardInfo.shouldDiscard() ? null : + SmartSpaceCard.fromWrapper(mContext, wrapper, newCardInfo.isPrimary()); + if (newCardInfo.isPrimary()) { + mData.mCurrentCard = smartSpaceCard; + } else { + mData.mWeatherCard = smartSpaceCard; + } + mData.handleExpire(); + update(); + + } + }); + } + }); + } + } + + + private void clearStore() { + ProtoStore protoStore = mStore; + StringBuilder sb = new StringBuilder(); + String str = "smartspace_"; + sb.append(str); + sb.append(mCurrentUserId); + sb.append("_true"); + protoStore.store(null, sb.toString()); + ProtoStore protoStore2 = mStore; + StringBuilder sb2 = new StringBuilder(); + sb2.append(str); + sb2.append(mCurrentUserId); + sb2.append("_false"); + protoStore2.store(null, sb2.toString()); + } + + public void update() { + Assert.isMainThread(); + String str = "SmartSpaceController"; + if (DEBUG) { + Log.d(str, "update"); + } + if (mAlarmRegistered) { + mAlarmManager.cancel(mExpireAlarmAction); + mAlarmRegistered = false; + } + long expiresAtMillis = mData.getExpiresAtMillis(); + if (expiresAtMillis > 0) { + mAlarmManager.set(0, expiresAtMillis, "SmartSpace", mExpireAlarmAction, mUiHandler); + mAlarmRegistered = true; + } + if (mListeners != null) { + if (DEBUG) { + StringBuilder sb = new StringBuilder(); + sb.append("notifying listeners data="); + sb.append(mData); + Log.d(str, sb.toString()); + } + ArrayList arrayList = new ArrayList(mListeners); + int size = arrayList.size(); + for (int i = 0; i < size; i++) { + ((SmartSpaceUpdateListener) arrayList.get(i)).onSmartSpaceUpdated(mData); + } + } + } + + public void onExpire(boolean z) { + Assert.isMainThread(); + mAlarmRegistered = false; + String str = "SmartSpaceController"; + if (mData.handleExpire() || z) { + update(); + if (UserHandle.myUserId() == 0) { + if (DEBUG) { + Log.d(str, "onExpire - sent"); + } + mAppContext.sendBroadcast(new Intent("com.google.android.systemui.smartspace.EXPIRE_EVENT").setPackage("com.google.android.googlequicksearchbox").addFlags(268435456)); + } + } else if (DEBUG) { + Log.d(str, "onExpire - cancelled"); + } + } + + public void setHideSensitiveData(boolean z) { + mHidePrivateData = z; + ArrayList arrayList = new ArrayList(mListeners); + for (int i = 0; i < arrayList.size(); i++) { + ((SmartSpaceUpdateListener) arrayList.get(i)).onSensitiveModeChanged(z); + } + if (mHidePrivateData) { + clearStore(); + } + } + + public void onGsaChanged() { + if (DEBUG) { + Log.d("SmartSpaceController", "onGsaChanged"); + } + if (UserHandle.myUserId() == 0) { + mAppContext.sendBroadcast(new Intent("com.google.android.systemui.smartspace.ENABLE_UPDATE").setPackage("com.google.android.googlequicksearchbox").addFlags(268435456)); + mSmartSpaceEnabledBroadcastSent = true; + } + ArrayList arrayList = new ArrayList(mListeners); + for (int i = 0; i < arrayList.size(); i++) { + ((SmartSpaceUpdateListener) arrayList.get(i)).onGsaChanged(); + } + } + + public void reloadData() { + mData.mCurrentCard = loadSmartSpaceData(true); + mData.mWeatherCard = loadSmartSpaceData(false); + update(); + } + + private boolean isSmartSpaceDisabledByExperiments() { + boolean z; + String string = Global.getString(mContext.getContentResolver(), "always_on_display_constants"); + KeyValueListParser keyValueListParser = new KeyValueListParser(','); + try { + keyValueListParser.setString(string); + z = keyValueListParser.getBoolean("smart_space_enabled", true); + } catch (IllegalArgumentException unused) { + Log.e("SmartSpaceController", "Bad AOD constants"); + z = true; + } + if (!z) { + return true; + } + return false; + } + + @Override + public void dump(FileDescriptor fileDescriptor, PrintWriter printWriter, String[] strArr) { + printWriter.println(); + printWriter.println("SmartspaceController"); + StringBuilder sb = new StringBuilder(); + sb.append(" initial broadcast: "); + sb.append(mSmartSpaceEnabledBroadcastSent); + printWriter.println(sb.toString()); + StringBuilder sb2 = new StringBuilder(); + String str = " weather "; + sb2.append(str); + sb2.append(mData.mWeatherCard); + printWriter.println(sb2.toString()); + StringBuilder sb3 = new StringBuilder(); + String str2 = " current "; + sb3.append(str2); + sb3.append(mData.mCurrentCard); + printWriter.println(sb3.toString()); + printWriter.println("serialized:"); + StringBuilder sb4 = new StringBuilder(); + sb4.append(str); + sb4.append(loadSmartSpaceData(false)); + printWriter.println(sb4.toString()); + StringBuilder sb5 = new StringBuilder(); + sb5.append(str2); + sb5.append(loadSmartSpaceData(true)); + printWriter.println(sb5.toString()); + StringBuilder sb6 = new StringBuilder(); + sb6.append("disabled by experiment: "); + sb6.append(isSmartSpaceDisabledByExperiments()); + printWriter.println(sb6.toString()); + } + + public void addListener(SmartSpaceUpdateListener smartSpaceUpdateListener) { + Assert.isMainThread(); + mListeners.add(smartSpaceUpdateListener); + SmartSpaceData smartSpaceData = mData; + if (smartSpaceData != null && smartSpaceUpdateListener != null) { + smartSpaceUpdateListener.onSmartSpaceUpdated(smartSpaceData); + } + } + + public void removeListener(SmartSpaceUpdateListener smartSpaceUpdateListener) { + Assert.isMainThread(); + mListeners.remove(smartSpaceUpdateListener); + } +} diff --git a/packages/SystemUI/src/com/google/android/systemui/smartspace/SmartSpaceData.java b/packages/SystemUI/src/com/google/android/systemui/smartspace/SmartSpaceData.java new file mode 100644 index 00000000000..50214c64a36 --- /dev/null +++ b/packages/SystemUI/src/com/google/android/systemui/smartspace/SmartSpaceData.java @@ -0,0 +1,95 @@ +package com.google.android.systemui.smartspace; + +import android.util.Log; + +public class SmartSpaceData { + SmartSpaceCard mCurrentCard; + SmartSpaceCard mWeatherCard; + + public boolean hasWeather() { + return mWeatherCard != null; + } + + public boolean hasCurrent() { + return mCurrentCard != null; + } + + public long getExpirationRemainingMillis() { + long expiration; + long currentTimeMillis = System.currentTimeMillis(); + if (hasCurrent() && hasWeather()) { + expiration = Math.min(mCurrentCard.getExpiration(), mWeatherCard.getExpiration()); + } else if (hasCurrent()) { + expiration = mCurrentCard.getExpiration(); + } else if (!hasWeather()) { + return 0; + } else { + expiration = mWeatherCard.getExpiration(); + } + return expiration - currentTimeMillis; + } + + public long getExpiresAtMillis() { + if (hasCurrent() && hasWeather()) { + return Math.min(mCurrentCard.getExpiration(), mWeatherCard.getExpiration()); + } + if (hasCurrent()) { + return mCurrentCard.getExpiration(); + } + if (hasWeather()) { + return mWeatherCard.getExpiration(); + } + return 0; + } + + public void clear() { + mWeatherCard = null; + mCurrentCard = null; + } + + public boolean handleExpire() { + boolean z; + String str = "SmartspaceData"; + if (!hasWeather() || !mWeatherCard.isExpired()) { + z = false; + } else { + if (SmartSpaceController.DEBUG) { + StringBuilder sb = new StringBuilder(); + sb.append("weather expired "); + sb.append(mWeatherCard.getExpiration()); + Log.d(str, sb.toString()); + } + mWeatherCard = null; + z = true; + } + if (!hasCurrent() || !mCurrentCard.isExpired()) { + return z; + } + if (SmartSpaceController.DEBUG) { + StringBuilder sb2 = new StringBuilder(); + sb2.append("current expired "); + sb2.append(mCurrentCard.getExpiration()); + Log.d(str, sb2.toString()); + } + mCurrentCard = null; + return true; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + sb.append(mCurrentCard); + sb.append(","); + sb.append(mWeatherCard); + sb.append("}"); + return sb.toString(); + } + + public SmartSpaceCard getWeatherCard() { + return mWeatherCard; + } + + public SmartSpaceCard getCurrentCard() { + return mCurrentCard; + } +} diff --git a/packages/SystemUI/src/com/google/android/systemui/smartspace/SmartSpaceUpdateListener.java b/packages/SystemUI/src/com/google/android/systemui/smartspace/SmartSpaceUpdateListener.java new file mode 100644 index 00000000000..2f4d399ec31 --- /dev/null +++ b/packages/SystemUI/src/com/google/android/systemui/smartspace/SmartSpaceUpdateListener.java @@ -0,0 +1,11 @@ +package com.google.android.systemui.smartspace; + +public interface SmartSpaceUpdateListener { + default void onGsaChanged(){ + } + + default void onSensitiveModeChanged(boolean z){ + } + + void onSmartSpaceUpdated(SmartSpaceData smartSpaceData); +} diff --git a/packages/SystemUI/src/com/google/android/systemui/smartspace/smartspace.proto b/packages/SystemUI/src/com/google/android/systemui/smartspace/smartspace.proto new file mode 100644 index 00000000000..384f6b5def9 --- /dev/null +++ b/packages/SystemUI/src/com/google/android/systemui/smartspace/smartspace.proto @@ -0,0 +1,92 @@ +syntax = "proto2"; + +package SmartspaceProto; + +option java_package = "com.google.android.systemui.smartspace"; +option java_outer_classname = "SmartspaceProto"; + +message SmartspaceUpdate { + message SmartspaceCard { + message ExpiryCriteria { + required int64 expirationTimeMillis = 1; + required int32 maxImpressions = 2; + } + + message Image { + required string key = 1; + required string gsaResourceName = 2; + required string uri = 3; + } + + message Message { + message FormattedText { + message FormatParam { + enum FormatParamArgs { + SOMETHING0 = 0; + SOMETHING1 = 1; + SOMETHING2 = 2; + SOMETHING3 = 3; + } + + required string text = 1; + required int32 truncateLocation = 2; + required FormatParamArgs formatParamArgs = 3; + required bool updateTimeLocally = 4; + } + + required string text = 1; + required int32 truncateLocation = 2; + repeated FormatParam formatParam = 3; + } + + required FormattedText title = 1; + required FormattedText subtitle = 2; + } + + message TapAction { + enum ActionType { + ACTION0 = 0; + ACTION1 = 1; + ACTION2 = 2; + } + + required ActionType actionType = 1; + required string intent = 2; + } + + enum CardType { + CARD0 = 0; + CARD1 = 1; + CARD2 = 2; + CARD3 = 3; + CARD4 = 4; + CARD5 = 5; + CARD6 = 6; + } + + required bool shouldDiscard = 1; + required int32 cardId = 2; + required Message preEvent = 3; + required Message duringEvent = 4; + required Message postEvent = 5; + required Image icon = 6; + required CardType cardType = 7; + required TapAction tapAction = 8; + required int64 updateTimeMillis = 9; + required int64 eventTimeMillis = 10; + required int64 eventDurationMillis = 11; + required ExpiryCriteria expiryCriteria = 12; + required int32 cardPriority =13; + } + + repeated SmartspaceCard card = 1; +} + +message CardWrapper { + required SmartspaceUpdate.SmartspaceCard card = 1; + required int64 publishTime = 2; + required int64 gsaUpdateTime = 3; + required int32 gsaVersionCode = 4; + required bytes icon = 5; + required bool isIconGrayscale = 6; +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/google/android/systemui/statusbar/phone/StatusBarGoogle.java b/packages/SystemUI/src/com/google/android/systemui/statusbar/phone/StatusBarGoogle.java new file mode 100644 index 00000000000..05078c86266 --- /dev/null +++ b/packages/SystemUI/src/com/google/android/systemui/statusbar/phone/StatusBarGoogle.java @@ -0,0 +1,31 @@ +package com.google.android.systemui.statusbar.phone; + +import com.android.systemui.Dependency; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.phone.StatusBar; +import com.google.android.systemui.NotificationLockscreenUserManagerGoogle; +import com.google.android.systemui.smartspace.SmartSpaceController; +import java.io.FileDescriptor; +import java.io.PrintWriter; + +public class StatusBarGoogle extends StatusBar { + + @Override + public void start() { + super.start(); + ((NotificationLockscreenUserManagerGoogle) Dependency.get(NotificationLockscreenUserManager.class)).updateAodVisibilitySettings(); + } + + @Override + public void setLockscreenUser(int i) { + super.setLockscreenUser(i); + SmartSpaceController.get(this.mContext).reloadData(); + } + + @Override + public void dump(FileDescriptor fileDescriptor, PrintWriter printWriter, String[] strArr) { + super.dump(fileDescriptor, printWriter, strArr); + SmartSpaceController.get(this.mContext).dump(fileDescriptor, printWriter, strArr); + } + +} diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk index 7b8d19952c5..cc52c91c596 100644 --- a/packages/overlays/Android.mk +++ b/packages/overlays/Android.mk @@ -43,6 +43,7 @@ LOCAL_REQUIRED_MODULES := \ IconPackRoundedSettingsOverlay \ IconPackRoundedSystemUIOverlay \ IconPackRoundedThemePickerUIOverlay \ + IconShapePebbleOverlay \ IconShapeRoundedRectOverlay \ IconShapeSquircleOverlay \ IconShapeTeardropOverlay \ diff --git a/packages/overlays/IconShapePebbleOverlay/Android.mk b/packages/overlays/IconShapePebbleOverlay/Android.mk new file mode 100644 index 00000000000..f0a730c81f8 --- /dev/null +++ b/packages/overlays/IconShapePebbleOverlay/Android.mk @@ -0,0 +1,31 @@ +# +# Copyright 2018, The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_RRO_THEME := IconShapePebble + +LOCAL_PRODUCT_MODULE := true + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := IconShapePebbleOverlay +LOCAL_SDK_VERSION := current + +include $(BUILD_RRO_PACKAGE) diff --git a/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml b/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml new file mode 100644 index 00000000000..10f42e2e867 --- /dev/null +++ b/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + diff --git a/packages/overlays/IconShapePebbleOverlay/res/values/config.xml b/packages/overlays/IconShapePebbleOverlay/res/values/config.xml new file mode 100644 index 00000000000..33afdf785b5 --- /dev/null +++ b/packages/overlays/IconShapePebbleOverlay/res/values/config.xml @@ -0,0 +1,28 @@ + + + + + MM55,0 C25,0 0,25 0,50 0,78 28,100 55,100 85,100 100,85 100,58 100,30 86,0 55,0 Z + + false + + 8.0dip + + 16.0dip + diff --git a/packages/overlays/IconShapePebbleOverlay/res/values/strings.xml b/packages/overlays/IconShapePebbleOverlay/res/values/strings.xml new file mode 100644 index 00000000000..aacec0e52b5 --- /dev/null +++ b/packages/overlays/IconShapePebbleOverlay/res/values/strings.xml @@ -0,0 +1,22 @@ + + + + + Pebble + diff --git a/services/core/Android.bp b/services/core/Android.bp index 9855e4ea339..5f0199ec45d 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -54,6 +54,7 @@ java_library_static { "dnsresolver_aidl_interface-V2-java", "netd_aidl_interface-V2-java", "netd_event_listener_interface-java", + "faceunlock_utils", ], } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index a47ea4f95b3..f0ac573fe73 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -428,6 +428,12 @@ public boolean isCharging() { } } + public void resetStatistics() { + synchronized (mStats) { + mStats.resetAllStatsCmdLocked(); + } + } + public long computeBatteryTimeRemaining() { synchronized (mStats) { long time = mStats.computeBatteryTimeRemaining(SystemClock.elapsedRealtime()); diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index af2f24f959b..98f359b3645 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -969,23 +969,23 @@ private Pair checkAndGetBiometricModality(int userId) { int firstHwAvailable = TYPE_NONE; for (int i = 0; i < mAuthenticators.size(); i++) { - modality = mAuthenticators.get(i).getType(); + int type = mAuthenticators.get(i).getType(); BiometricAuthenticator authenticator = mAuthenticators.get(i).getAuthenticator(); if (authenticator.isHardwareDetected()) { isHardwareDetected = true; if (firstHwAvailable == TYPE_NONE) { // Store the first one since we want to return the error in correct priority // order. - firstHwAvailable = modality; + firstHwAvailable = type; } if (authenticator.hasEnrolledTemplates(userId)) { hasTemplatesEnrolled = true; - if (isEnabledForApp(modality, userId)) { + if (isEnabledForApp(type, userId)) { // TODO(b/110907543): When face settings (and other settings) have both a // user toggle as well as a work profile settings page, this needs to be // updated to reflect the correct setting. enabledForApps = true; - break; + modality |= type; } } } @@ -1132,14 +1132,14 @@ private int statsModality() { if (mCurrentAuthSession == null) { return BiometricsProtoEnums.MODALITY_UNKNOWN; } - if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_FINGERPRINT) + if ((mCurrentAuthSession.mModality & TYPE_FINGERPRINT) != 0) { modality |= BiometricsProtoEnums.MODALITY_FINGERPRINT; } - if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_IRIS) != 0) { + if ((mCurrentAuthSession.mModality & TYPE_IRIS) != 0) { modality |= BiometricsProtoEnums.MODALITY_IRIS; } - if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_FACE) != 0) { + if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) { modality |= BiometricsProtoEnums.MODALITY_FACE; } return modality; @@ -1185,6 +1185,7 @@ private void handleAuthenticationSucceeded(boolean requireConfirmation, byte[] t mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); mCurrentAuthSession.mState = STATE_AUTH_IDLE; mCurrentAuthSession = null; + cancelInternal(null, null, false); } else { mCurrentAuthSession.mAuthenticatedTimeMs = System.currentTimeMillis(); // Store the auth token and submit it to keystore after the confirmation @@ -1195,7 +1196,7 @@ private void handleAuthenticationSucceeded(boolean requireConfirmation, byte[] t // Notify SysUI that the biometric has been authenticated. SysUI already knows // the implicit/explicit state and will react accordingly. - mStatusBarService.onBiometricAuthenticated(true, null /* failureReason */); + mStatusBarService.onBiometricAuthenticated(true, null /* failureReason */, requireConfirmation); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } @@ -1210,7 +1211,7 @@ private void handleAuthenticationFailed(String failureReason) { return; } - mStatusBarService.onBiometricAuthenticated(false, failureReason); + mStatusBarService.onBiometricAuthenticated(false, failureReason, false); // TODO: This logic will need to be updated if BP is multi-modal if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) { @@ -1395,6 +1396,9 @@ private void handleOnDismissed(int reason) { // to the application KeyStore.getInstance().addAuthToken(mCurrentAuthSession.mTokenEscrow); mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); + // If we are using multiple modalities, we cancel the other modality that still + // might be listening for authentication + cancelInternal(null /* token */, null /* package */, false /* fromClient */); } // Do not clean up yet if we are from ConfirmDeviceCredential. We should be in the @@ -1426,6 +1430,7 @@ private void handleOnTryAgainPressed() { mCurrentAuthSession.mCallingUserId, mCurrentAuthSession.mModality, mCurrentAuthSession.mConfirmDeviceCredentialCallback); + mCurrentAuthSession = null; } private void handleOnReadyForAuthentication(int cookie, boolean requireConfirmation, @@ -1458,14 +1463,14 @@ private void handleOnReadyForAuthentication(int cookie, boolean requireConfirmat it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator(); while (it.hasNext()) { Map.Entry pair = (Map.Entry) it.next(); - if (pair.getKey() == TYPE_FINGERPRINT) { + if ((pair.getKey() & TYPE_FINGERPRINT) != 0) { mFingerprintService.startPreparedClient(pair.getValue()); - } else if (pair.getKey() == TYPE_IRIS) { + } + if ((pair.getKey() & TYPE_IRIS) != 0) { Slog.e(TAG, "Iris unsupported"); - } else if (pair.getKey() == TYPE_FACE) { + } + if ((pair.getKey() & TYPE_FACE) != 0) { mFaceService.startPreparedClient(pair.getValue()); - } else { - Slog.e(TAG, "Unknown modality: " + pair.getKey()); } modality |= pair.getKey(); } @@ -1555,13 +1560,9 @@ private void authenticateInternal(IBinder token, long sessionId, int userId, Slog.d(TAG, "Creating auth session. Modality: " + modality + ", cookie: " + cookie); final HashMap authenticators = new HashMap<>(); - authenticators.put(modality, cookie); - mPendingAuthSession = new AuthSession(authenticators, token, sessionId, userId, - receiver, opPackageName, bundle, callingUid, callingPid, callingUserId, - modality, requireConfirmation, callback); - mPendingAuthSession.mState = STATE_AUTH_CALLED; // No polymorphism :( if ((modality & TYPE_FINGERPRINT) != 0) { + authenticators.put(TYPE_FINGERPRINT, cookie); mFingerprintService.prepareForAuthentication(token, sessionId, userId, mInternalReceiver, opPackageName, cookie, callingUid, callingPid, callingUserId); @@ -1570,10 +1571,15 @@ private void authenticateInternal(IBinder token, long sessionId, int userId, Slog.w(TAG, "Iris unsupported"); } if ((modality & TYPE_FACE) != 0) { + authenticators.put(TYPE_FACE, cookie); mFaceService.prepareForAuthentication(requireConfirmation, token, sessionId, userId, mInternalReceiver, opPackageName, cookie, callingUid, callingPid, callingUserId); } + mPendingAuthSession = new AuthSession(authenticators, token, sessionId, userId, + receiver, opPackageName, bundle, callingUid, callingPid, callingUserId, + modality, requireConfirmation, callback); + mPendingAuthSession.mState = STATE_AUTH_CALLED; } catch (RemoteException e) { Slog.e(TAG, "Unable to start authentication", e); } @@ -1626,6 +1632,11 @@ private void handleCancelAuthentication(IBinder token, String opPackageName) { if (fromCDC) { if (DEBUG) Slog.d(TAG, "Cancelling from CDC"); cancelInternal(token, opPackageName, false /* fromClient */); + try { + mStatusBarService.hideBiometricDialog(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } } else { cancelInternal(token, opPackageName, true /* fromClient */); } diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index 387d7a85f4a..4d221fa2daf 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -27,8 +27,14 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; @@ -46,6 +52,7 @@ import android.os.Binder; import android.os.Build; import android.os.Environment; +import android.os.Handler; import android.os.IBinder; import android.os.NativeHandle; import android.os.RemoteException; @@ -55,10 +62,12 @@ import android.os.UserManager; import android.provider.Settings; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.server.SystemServerInitThreadPool; import com.android.server.biometrics.AuthenticationClient; @@ -84,6 +93,10 @@ import java.util.List; import java.util.Map; +import com.android.internal.util.custom.faceunlock.FaceUnlockUtils; +import com.motorola.internal.app.IMotoFaceService; +import com.motorola.internal.app.IMotoFaceServiceReceiver; + /** * A service to manage multiple clients that want to access the face HAL API. * The service is responsible for maintaining a list of clients and dispatching all @@ -103,6 +116,230 @@ public class FaceService extends BiometricServiceBase { private static final String NOTIFICATION_TAG = "FaceService"; private static final int NOTIFICATION_ID = 1; + + /** Start moto changes */ + + private static final int MOTO_DEVICE_ID = 1108; + private static final String BIND_MOTOFACEID_ACTION = "com.motorola.faceunlock.BIND"; + private static final String PACKAGE_MOTOFACEID_PACKAGE_NAME = "com.motorola.faceunlock"; + private static final String PACKAGE_MOTOFACEID_SERVICE_NAME = "com.motorola.faceunlock.service.FaceAuthService"; + + SparseArray mMotoFaceServices = new SparseArray<>(); + IMotoFaceServiceReceiver mMotoReceiver = new IMotoFaceServiceReceiver.Stub() { + @Override + public void onEnrollResult(int faceId, int userId, int remaining) { + mHandler.post(new Runnable() { + @Override + public final void run() { + FaceService.super.handleEnrollResult(new Face(getBiometricUtils().getUniqueName(getContext(), userId), faceId, MOTO_DEVICE_ID), remaining); + } + }); + } + + @Override + public void onAuthenticated(int faceId, int userId, byte[] token) { + mHandler.post(new Runnable() { + @Override + public final void run() { + Face face = new Face("", faceId, MOTO_DEVICE_ID); + ArrayList token_AL = new ArrayList<>(token.length); + for (byte b : token) { + token_AL.add(new Byte(b)); + } + FaceService.super.handleAuthenticated(face, token_AL); + } + }); + } + + @Override + public void onAcquired(int userId, int acquiredInfo, int vendorCode) { + mHandler.post(new Runnable() { + @Override + public final void run() { + FaceService.super.handleAcquired(MOTO_DEVICE_ID, acquiredInfo, vendorCode); + } + }); + } + + @Override + public void onError(int error, int vendorCode) { + mHandler.post(new Runnable() { + @Override + public final void run() { + FaceService.super.handleError(MOTO_DEVICE_ID, error, vendorCode); + } + }); + } + + @Override + public void onRemoved(int[] faceIds, int userId) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public final void run() { + if (faceIds.length > 0) { + for (int i = 0; i < faceIds.length; i++) { + FaceService.super.handleRemoved(new Face("", faceIds[i], MOTO_DEVICE_ID), (faceIds.length - i) - 1); + } + return; + } + FaceService.super.handleRemoved(new Face("", 0, MOTO_DEVICE_ID), 0); + } + }); + } + + @Override + public void onEnumerate(int[] faceIds, int userId) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public final void run() { + if (faceIds.length > 0) { + for (int i = 0; i < faceIds.length; i++) { + FaceService.super.handleEnumerate(new Face("", faceIds[i], MOTO_DEVICE_ID), (faceIds.length - i) - 1); + } + return; + } + FaceService.super.handleEnumerate(null, 0); + } + }); + } + + @Override + public void onLockoutChanged(long duration) throws RemoteException { + if (duration == 0) { + mCurrentUserLockoutMode = 0; + } else if (duration == Long.MAX_VALUE) { + mCurrentUserLockoutMode = 2; + } else { + mCurrentUserLockoutMode = 1; + } + mHandler.post(new Runnable() { + @Override + public final void run() { + if (duration == 0) { + notifyLockoutResetMonitors(); + } + } + }); + } + }; + private Handler mMotoServiceHandler; + public boolean mIsMotoServiceBinding = false; + private static final boolean mUseMotoFaceUnlockService = FaceUnlockUtils.hasMotoFaceUnlock(); + private final BroadcastReceiver mUserUnlockReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mUseMotoFaceUnlockService) { + if (getMotoFaceService(mCurrentUserId) == null) { + bindMotoFaceAuthService(mCurrentUserId); + } + } + } + }; + + private class MotoFaceServiceConnection implements ServiceConnection { + int mUserId; + + public MotoFaceServiceConnection(int userId) { + mUserId = userId; + } + + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + Slog.d(TAG, "MotoFaceService connected"); + IMotoFaceService motoFaceService = IMotoFaceService.Stub.asInterface(service); + if (motoFaceService != null) { + synchronized (mMotoFaceServices) { + try { + motoFaceService.setCallback(mMotoReceiver); + motoFaceService.asBinder().linkToDeath(new IBinder.DeathRecipient() { + @Override + public void binderDied() { + Slog.e(TAG, "MotoFaceService binder died"); + mMotoFaceServices.remove(mUserId); + if (mUserId == mCurrentUserId) { + boolean unused = bindMotoFaceAuthService(mUserId); + } + } + }, 0); + mMotoFaceServices.put(mUserId, motoFaceService); + mHandler.post(new Runnable() { + @Override + public final void run() { + if (mMotoFaceServices.size() == 1) { + loadAuthenticatorIds(); + } + updateActiveGroup(mUserId, null); + doTemplateCleanupForUser(mUserId); + } + }); + } catch (RemoteException e) { + e.printStackTrace(); + } + mIsMotoServiceBinding = false; + } + } + } + + @Override + public void onServiceDisconnected(ComponentName className) { + Slog.d(TAG, "MotoFaceService disconnected"); + mMotoFaceServices.remove(mUserId); + mIsMotoServiceBinding = false; + if (mUserId == mCurrentUserId) { + bindMotoFaceAuthService(mUserId); + } + } + } + + private boolean isMotoFaceServiceEnabled() { + PackageManager pm = getContext().getPackageManager(); + if (!mUseMotoFaceUnlockService) { + return false; + } + Intent intent = new Intent(BIND_MOTOFACEID_ACTION); + intent.setClassName(PACKAGE_MOTOFACEID_PACKAGE_NAME, PACKAGE_MOTOFACEID_SERVICE_NAME); + ResolveInfo info = pm.resolveService(intent, 131072); + if (info == null || !info.serviceInfo.isEnabled()) { + return false; + } + return true; + } + + private IMotoFaceService getMotoFaceService(int userId) { + if (userId == UserHandle.USER_NULL) { + updateActiveGroup(ActivityManager.getCurrentUser(), null); + } + return mMotoFaceServices.get(mCurrentUserId); + } + + private boolean bindMotoFaceAuthService(int userId) { + Slog.d(TAG, "bindMotoFaceAuthService"); + if (!isMotoFaceServiceEnabled()) { + Slog.d(TAG, "MotoFaceService disabled"); + return false; + } else if (mIsMotoServiceBinding) { + Slog.d(TAG, "MotoFaceService is binding"); + return true; + } else { + if (userId != UserHandle.USER_NULL && getMotoFaceService(userId) == null) { + try { + Intent intent = new Intent(BIND_MOTOFACEID_ACTION); + intent.setClassName(PACKAGE_MOTOFACEID_PACKAGE_NAME, PACKAGE_MOTOFACEID_SERVICE_NAME); + boolean result = getContext().bindServiceAsUser(intent, new MotoFaceServiceConnection(userId), 65, UserHandle.of(userId)); + if (result) { + mIsMotoServiceBinding = true; + } + return result; + } catch (Exception e) { + Slog.e(TAG, "bindMotoFaceAuthService failed", e); + } + } + return false; + } + } + + /* End moto changes*/ + /** * Events for bugreports. */ @@ -219,11 +456,17 @@ protected int statsModality() { @Override public boolean shouldFrameworkHandleLockout() { + if (mUseMotoFaceUnlockService){ + return true; + } return false; } @Override public boolean wasUserDetected() { + if (mUseMotoFaceUnlockService){ + return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED; + } return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED && mLastAcquire != FaceManager.FACE_ACQUIRED_SENSOR_DIRTY; } @@ -361,6 +604,42 @@ public int revokeChallenge(IBinder token) { return Status.OK; } + @Override // Binder call + public void enrollMoto(final IBinder token, final byte[] cryptoToken, + final IFaceServiceReceiver receiver, final String opPackageName, + final int[] disabledFeatures) { + checkPermission(MANAGE_BIOMETRIC); + + final boolean restricted = isRestricted(); + final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper, + mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, + 0 /* groupId */, cryptoToken, restricted, opPackageName, disabledFeatures, + ENROLL_TIMEOUT_SEC) { + + @Override + public int[] getAcquireIgnorelist() { + return mEnrollIgnoreList; + } + + @Override + public int[] getAcquireVendorIgnorelist() { + return mEnrollIgnoreListVendor; + } + + @Override + public boolean shouldVibrate() { + return false; + } + + @Override + protected int statsModality() { + return FaceService.this.statsModality(); + } + }; + + enrollInternal(client, mCurrentUserId); + } + @Override // Binder call public void enroll(int userId, final IBinder token, final byte[] cryptoToken, final IFaceServiceReceiver receiver, final String opPackageName, @@ -542,6 +821,20 @@ public boolean isHardwareDetected(long deviceId, String opPackageName) { UserHandle.getCallingUserId())) { return false; } + if (mUseMotoFaceUnlockService) { + boolean enabled = isMotoFaceServiceEnabled(); + if (enabled) { + mHandler.post(new Runnable() { + @Override + public final void run() { + if (getMotoFaceService(mCurrentUserId) == null) { + bindMotoFaceAuthService(mCurrentUserId); + } + } + }); + } + return enabled; + } final long token = Binder.clearCallingIdentity(); try { @@ -977,6 +1270,20 @@ public void onLockoutChanged(long duration) { private final DaemonWrapper mDaemonWrapper = new DaemonWrapper() { @Override public int authenticate(long operationId, int groupId) throws RemoteException { + if (mUseMotoFaceUnlockService) { + IMotoFaceService service = getMotoFaceService(mCurrentUserId); + if (service != null) { + try{ + service.authenticate(operationId); + } catch (Exception e) { + Slog.e(TAG, "authenticate failed", e); + } + return 0; + } + bindMotoFaceAuthService(mCurrentUserId); + Slog.w(TAG, "authenticate(): moto face service not started!"); + return 3; + } IBiometricsFace daemon = getFaceDaemon(); if (daemon == null) { Slog.w(TAG, "authenticate(): no face HAL!"); @@ -987,6 +1294,19 @@ public int authenticate(long operationId, int groupId) throws RemoteException { @Override public int cancel() throws RemoteException { + if (mUseMotoFaceUnlockService) { + IMotoFaceService service = getMotoFaceService(mCurrentUserId); + if (service == null) { + return 0; + } + try{ + service.cancel(); + }catch (Exception e) { + Slog.e(TAG, "cancel failed", e); + } + service.cancel(); + return 0; + } IBiometricsFace daemon = getFaceDaemon(); if (daemon == null) { Slog.w(TAG, "cancel(): no face HAL!"); @@ -997,6 +1317,20 @@ public int cancel() throws RemoteException { @Override public int remove(int groupId, int biometricId) throws RemoteException { + if (mUseMotoFaceUnlockService) { + IMotoFaceService service = getMotoFaceService(mCurrentUserId); + if (service != null) { + try{ + service.remove(biometricId); + }catch (Exception e) { + Slog.e(TAG, "remove failed", e); + } + return 0; + } + bindMotoFaceAuthService(mCurrentUserId); + Slog.w(TAG, "remove(): moto face service not started!"); + return 3; + } IBiometricsFace daemon = getFaceDaemon(); if (daemon == null) { Slog.w(TAG, "remove(): no face HAL!"); @@ -1007,6 +1341,26 @@ public int remove(int groupId, int biometricId) throws RemoteException { @Override public int enumerate() throws RemoteException { + if (mUseMotoFaceUnlockService) { + IMotoFaceService service = getMotoFaceService(mCurrentUserId); + if (service != null) { + mMotoServiceHandler.post(new Runnable() { + @Override + public final void run() { + try { + service.enumerate(); + } catch (Exception e) { + Slog.e(TAG, "enumerate failed", e); + FaceService.super.handleError(MOTO_DEVICE_ID, 8, 0); + } + } + }); + return 0; + } + bindMotoFaceAuthService(mCurrentUserId); + Slog.w(TAG, "enumerate(): moto face service not started!"); + return 3; + } IBiometricsFace daemon = getFaceDaemon(); if (daemon == null) { Slog.w(TAG, "enumerate(): no face HAL!"); @@ -1018,6 +1372,27 @@ public int enumerate() throws RemoteException { @Override public int enroll(byte[] cryptoToken, int groupId, int timeout, ArrayList disabledFeatures) throws RemoteException { + if (mUseMotoFaceUnlockService) { + IMotoFaceService service = getMotoFaceService(mCurrentUserId); + int[] dfs = null; + if (disabledFeatures != null && disabledFeatures.size() > 0) { + dfs = new int[disabledFeatures.size()]; + for (int i = 0; i < disabledFeatures.size(); i++) { + dfs[i] = disabledFeatures.get(i).intValue(); + } + } + if (service != null) { + try{ + service.enroll(cryptoToken, timeout, dfs); + }catch (Exception e) { + Slog.e(TAG, "enroll failed", e); + } + return 0; + } + bindMotoFaceAuthService(mCurrentUserId); + Slog.w(FaceService.TAG, "enroll(): moto face service not started!"); + return 3; + } IBiometricsFace daemon = getFaceDaemon(); if (daemon == null) { Slog.w(TAG, "enroll(): no face HAL!"); @@ -1032,6 +1407,20 @@ public int enroll(byte[] cryptoToken, int groupId, int timeout, @Override public void resetLockout(byte[] cryptoToken) throws RemoteException { + if (mUseMotoFaceUnlockService) { + IMotoFaceService service = getMotoFaceService(mCurrentUserId); + if (service != null) { + try{ + service.resetLockout(cryptoToken); + }catch (Exception e) { + Slog.e(TAG, "resetLockout failed", e); + } + return; + } + bindMotoFaceAuthService(mCurrentUserId); + Slog.w(TAG, "resetLockout(): moto face service not started!"); + return; + } IBiometricsFace daemon = getFaceDaemon(); if (daemon == null) { Slog.w(TAG, "resetLockout(): no face HAL!"); @@ -1065,6 +1454,8 @@ public FaceService(Context context) { .getIntArray(R.array.config_face_acquire_enroll_ignorelist); mEnrollIgnoreListVendor = getContext().getResources() .getIntArray(R.array.config_face_acquire_vendor_enroll_ignorelist); + + context.registerReceiver(mUserUnlockReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED)); } @Override @@ -1080,6 +1471,11 @@ protected void removeClient(ClientMonitor client) { public void onStart() { super.onStart(); publishBinderService(Context.FACE_SERVICE, new FaceServiceWrapper()); + if (mUseMotoFaceUnlockService) { + mMotoServiceHandler = BackgroundThread.getHandler(); + mHalDeviceId = MOTO_DEVICE_ID; + return; + } // Get the face daemon on FaceService's on thread so SystemServerInitThreadPool isn't // blocked SystemServerInitThreadPool.get().submit(() -> mHandler.post(this::getFaceDaemon), @@ -1128,6 +1524,28 @@ public void serviceDied(long cookie) { @Override protected void updateActiveGroup(int userId, String clientPackage) { + if (mUseMotoFaceUnlockService) { + mCurrentUserId = userId; + IMotoFaceService service = getMotoFaceService(mCurrentUserId); + if (service != null) { + try { + Map map = mAuthenticatorIds; + Integer valueOf = Integer.valueOf(mCurrentUserId); + long authId = 0; + if (hasEnrolledBiometrics(mCurrentUserId)) { + authId = (long) service.getAuthenticatorId(); + } + map.put(valueOf, Long.valueOf(authId)); + } catch (Exception e) { + Slog.e(TAG, "getAuthenticatorId failed", e); + } + } else { + bindMotoFaceAuthService(mCurrentUserId); + Slog.w(TAG, "updateActiveGroup(): moto face service not started!"); + } + return; + } + IBiometricsFace daemon = getFaceDaemon(); if (daemon != null) { @@ -1178,7 +1596,16 @@ protected long getHalDeviceId() { @Override protected void handleUserSwitching(int userId) { - super.handleUserSwitching(userId); + if (mUseMotoFaceUnlockService) { + updateActiveGroup(userId, null); + if (getMotoFaceService(userId) != null) { + doTemplateCleanupForUser(userId); + } else { + bindMotoFaceAuthService(userId); + } + } else { + super.handleUserSwitching(userId); + } // Will be updated when we get the callback from HAL mCurrentUserLockoutMode = AuthenticationClient.LOCKOUT_NONE; } @@ -1267,6 +1694,20 @@ private synchronized IBiometricsFace getFaceDaemon() { } private long startGenerateChallenge(IBinder token) { + if (mUseMotoFaceUnlockService) { + IMotoFaceService service = getMotoFaceService(mCurrentUserId); + if (service != null) { + try { + return service.generateChallenge(CHALLENGE_TIMEOUT_SEC); + } catch (Exception e) { + Slog.e(TAG, "generateChallenge failed", e); + } + } else { + bindMotoFaceAuthService(mCurrentUserId); + Slog.w(TAG, "startGenerateChallenge(): moto face service not started!"); + } + return 0; + } IBiometricsFace daemon = getFaceDaemon(); if (daemon == null) { Slog.w(TAG, "startGenerateChallenge: no face HAL!"); @@ -1281,6 +1722,17 @@ private long startGenerateChallenge(IBinder token) { } private int startRevokeChallenge(IBinder token) { + if (mUseMotoFaceUnlockService) { + IMotoFaceService service = getMotoFaceService(mCurrentUserId); + if (service != null) { + try { + return service.revokeChallenge(); + } catch (Exception e) { + Slog.e(TAG, "startRevokeChallenge failed", e); + } + } + return 0; + } IBiometricsFace daemon = getFaceDaemon(); if (daemon == null) { Slog.w(TAG, "startRevokeChallenge: no face HAL!"); @@ -1355,7 +1807,8 @@ private void dumpHal(FileDescriptor fd, String[] args) { // Additionally, this flag allows turning off face for a device // (either permanently through the build or on an individual device). if (SystemProperties.getBoolean("ro.face.disable_debug_data", false) - || SystemProperties.getBoolean("persist.face.disable_debug_data", false)) { + || SystemProperties.getBoolean("persist.face.disable_debug_data", false) + || mUseMotoFaceUnlockService) { return; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 63302705b36..5a812f0295a 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -219,6 +219,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher"; + // Long screenshot + private static final long LONGSHOT_BLOCK_SHOWING_TIMEOUT = 1000; + /** * Binding flags for establishing connection to the {@link InputMethodService}. */ @@ -294,6 +297,9 @@ private static final class DebugFlags { new DebugFlag("persist.pre_render_ime_views", false); } + // Long screenshot + private boolean mLongshotBlockShowing = false; + @UserIdInt private int mLastSwitchUserId; @@ -2795,6 +2801,11 @@ boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) { return false; } + if (mLongshotBlockShowing) { + Slog.d(TAG, "Longshot Blocking"); + return false; + } + boolean res = false; if (mCurMethod != null) { if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken); @@ -2865,6 +2876,27 @@ public boolean hideSoftInput(IInputMethodClient client, int flags, } } + @Override + public boolean hideSoftInputForLongshot(int flags, ResultReceiver resultReceiver) { + synchronized (mMethodMap) { + if (!calledFromValidUserLocked()) { + return false; + } + final long ident = Binder.clearCallingIdentity(); + try { + mLongshotBlockShowing = true; + mHandler.postDelayed(new Runnable() { + public void run() { + mLongshotBlockShowing = false; + } + }, LONGSHOT_BLOCK_SHOWING_TIMEOUT); + return hideCurrentInputLocked(flags, resultReceiver); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) { if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 && (mShowExplicitlyRequested || mShowForced)) { diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 3dd730471dc..080a50d3501 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -1512,6 +1512,11 @@ public boolean hideSoftInput( } } + @Override + public boolean hideSoftInputForLongshot(int flags, ResultReceiver resultReceiver) { + return false; + } + @BinderThread @Override public InputBindResult startInputOrWindowGainedFocus( diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 81e1b53a66d..782c94d9b40 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -120,6 +120,7 @@ import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult; import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken; import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager; +import com.android.server.wm.AppLockService; import com.android.server.wm.WindowManagerInternal; import libcore.util.HexEncoding; @@ -2119,6 +2120,7 @@ private void notifyPasswordChanged(@UserIdInt int userId) { mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); dpm.reportPasswordChanged(userId); LocalServices.getService(WindowManagerInternal.class).reportPasswordChanged(userId); + LocalServices.getService(AppLockService.class).reportPasswordChanged(userId); }); } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index ecf66861a40..a2e3a9800d2 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -657,6 +657,10 @@ private void grantDefaultSystemHandlerPermissions(int userId) { getDefaultSystemHandlerActivityPackage(musicIntent, userId), userId, STORAGE_PERMISSIONS); + // Google Markup + grantSystemFixedPermissionsToSystemPackage("com.google.android.markup", userId, + STORAGE_PERMISSIONS); + // Home Intent homeIntent = new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_HOME) diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 1fda5e6dbde..5b23bf1324f 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -130,6 +130,10 @@ import android.database.ContentObserver; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; +import android.hardware.camera2.CameraManager.TorchCallback; import android.hardware.display.DisplayManager; import android.hardware.hdmi.HdmiAudioSystemClient; import android.hardware.hdmi.HdmiControlManager; @@ -170,6 +174,7 @@ import android.service.vr.IPersistentVrStateCallbacks; import android.speech.RecognizerIntent; import android.telecom.TelecomManager; +import android.text.TextUtils; import android.util.Log; import android.util.LongSparseArray; import android.util.MutableBoolean; @@ -201,6 +206,7 @@ import com.android.internal.R; import com.android.internal.accessibility.AccessibilityShortcutController; +import com.android.internal.custom.longshot.ILongScreenshotManager; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.os.DeviceKeyHandler; @@ -281,6 +287,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3; 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; // must match: config_veryLongPresOnPowerBehavior in config.xml static final int VERY_LONG_PRESS_POWER_NOTHING = 0; @@ -563,6 +570,9 @@ public void onDrawn() { // Whether to support long press from power button in non-interactive mode private boolean mSupportLongPressPowerWhenNonInteractive; + // Power long press action saved on key down that should happen on key up + private int mResolvedLongPressOnPowerBehavior; + // Whether to go to sleep entering theater mode from power button private boolean mGoToSleepOnButtonPressTheaterMode; @@ -632,6 +642,34 @@ public void onDrawn() { private final List mDeviceKeyHandlers = new ArrayList<>(); + private CameraManager mCameraManager; + private String mCameraId; + private int mTorchActionMode; + private boolean mTorchEnabled = false; + private TorchCallback mTorchCallback = new TorchCallback() { + @Override + public void onTorchModeChanged(String cameraId, boolean enabled) { + if (!TextUtils.isEmpty(mCameraId)) { + if (mCameraId.equals(cameraId)) { + mTorchEnabled = enabled; + } + } else { + mTorchEnabled = enabled; + } + } + + @Override + public void onTorchModeUnavailable(String cameraId) { + if (!TextUtils.isEmpty(mCameraId)) { + if (mCameraId.equals(cameraId)) { + mTorchEnabled = false; + } + } else { + mTorchEnabled = false; + } + } + }; + 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; @@ -657,6 +695,7 @@ public void onDrawn() { private static final int MSG_NOTIFY_USER_ACTIVITY = 26; private static final int MSG_RINGER_TOGGLE_CHORD = 27; private static final int MSG_MOVE_DISPLAY_TO_TOP = 28; + private static final int MSG_TOGGLE_TORCH = 50; // Global actions on lockk screen private boolean mGlobalActionsOnLockDisable; @@ -755,6 +794,10 @@ public void handleMessage(Message msg) { mWindowManagerFuncs.moveDisplayToTop(msg.arg1); mMovingDisplayToTopKeyTriggered = false; break; + case MSG_TOGGLE_TORCH: + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, true, "Toggle Torch"); + toggleFlashLight(); + break; } } } @@ -820,6 +863,9 @@ void observe() { Settings.System.LOCK_POWER_MENU_DISABLED), false, this); resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.SWIPE_TO_SCREENSHOT), false, this); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.TORCH_POWER_BUTTON_GESTURE), false, this, + UserHandle.USER_ALL); updateSettings(); } @@ -1015,6 +1061,7 @@ private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { // When interactive, we're already awake. // Wait for a long press or for the button to be released to decide what to do. if (hasLongPressOnPowerBehavior()) { + mResolvedLongPressOnPowerBehavior = getResolvedLongPressOnPowerBehavior(); if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) { powerLongPress(); } else { @@ -1031,9 +1078,12 @@ private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { } } } else { - wakeUpFromPowerKey(event.getDownTime()); - - if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) { + if ((mTorchActionMode == 1) || (mSupportLongPressPowerWhenNonInteractive + && hasLongPressOnPowerBehavior())) { + mResolvedLongPressOnPowerBehavior = getResolvedLongPressOnPowerBehavior(); + if (mResolvedLongPressOnPowerBehavior != LONG_PRESS_POWER_TORCH) { + wakeUpFromPowerKey(event.getDownTime()); + } if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) { powerLongPress(); } else { @@ -1051,6 +1101,7 @@ private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { mBeganFromNonInteractive = true; } else { + wakeUpFromPowerKey(event.getDownTime()); final int maxCount = getMaxMultiPressPowerCount(); if (maxCount <= 1) { @@ -1063,6 +1114,18 @@ private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { } } + private boolean isDozeMode() { + IDreamManager dreamManager = getDreamManager(); + try { + if (dreamManager != null && dreamManager.isDreaming()) { + return true; + } + } catch (RemoteException e) { + return false; + } + return false; + } + private void interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled) { final boolean handled = canceled || mPowerKeyHandled; mScreenshotChordPowerKeyTriggered = false; @@ -1110,6 +1173,11 @@ private void cancelPendingPowerKeyAction() { if (!mPowerKeyHandled) { mPowerKeyHandled = true; mHandler.removeMessages(MSG_POWER_LONG_PRESS); + // See if we deferred screen wake because long press power for torch is enabled + if (mResolvedLongPressOnPowerBehavior == LONG_PRESS_POWER_TORCH + && (!isScreenOn() || isDozeMode())) { + wakeUpFromPowerKey(SystemClock.uptimeMillis()); + } } if (hasVeryLongPressOnPowerBehavior()) { mHandler.removeMessages(MSG_POWER_VERY_LONG_PRESS); @@ -1125,7 +1193,7 @@ private void cancelPendingBackKeyAction() { } private void powerPress(long eventTime, boolean interactive, int count) { - if (mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) { + if (!isDozeMode() && mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) { Slog.i(TAG, "Suppressed redundant power key press while " + "already in the process of turning the screen on."); return; @@ -1172,6 +1240,8 @@ private void powerPress(long eventTime, boolean interactive, int count) { break; } } + } else if ((mTorchActionMode == 1) && (!isScreenOn() || isDozeMode())) { + wakeUpFromPowerKey(eventTime); } } @@ -1256,6 +1326,32 @@ private void powerMultiPressAction(long eventTime, boolean interactive, int beha } } + private String getCameraId() throws CameraAccessException { + String[] ids = mCameraManager.getCameraIdList(); + if (ids != null && ids.length > 0) { + for (String id : ids) { + CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id); + Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); + Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING); + if (flashAvailable != null && flashAvailable + && lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) { + return id; + } + } + } + return ""; + } + + private void toggleFlashLight() { + try { + mCameraManager.setTorchMode(mCameraId, !mTorchEnabled); + } catch (CameraAccessException e) { + + } catch (IllegalArgumentException e) { + + } + } + private int getLidBehavior() { return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.LID_BEHAVIOR, LID_BEHAVIOR_NONE); @@ -1272,7 +1368,7 @@ private int getMaxMultiPressPowerCount() { } private void powerLongPress() { - final int behavior = getResolvedLongPressOnPowerBehavior(); + final int behavior = mResolvedLongPressOnPowerBehavior; switch (behavior) { case LONG_PRESS_POWER_NOTHING: break; @@ -1304,6 +1400,15 @@ private void powerLongPress() { final int powerKeyDeviceId = Integer.MIN_VALUE; launchAssistAction(null, powerKeyDeviceId); break; + case LONG_PRESS_POWER_TORCH: + mPowerKeyHandled = true; + // Toggle torch state asynchronously to help protect against + // a misbehaving cameraservice from blocking systemui. + mHandler.removeMessages(MSG_TOGGLE_TORCH); + Message msg = mHandler.obtainMessage(MSG_TOGGLE_TORCH); + msg.setAsynchronous(true); + msg.sendToTarget(); + break; } } @@ -1355,6 +1460,9 @@ private int getResolvedLongPressOnPowerBehavior() { if (FactoryTest.isLongPressOnPowerOffEnabled()) { return LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM; } + if ((mTorchActionMode == 1) && (!isScreenOn() || isDozeMode() || mTorchEnabled)) { + return LONG_PRESS_POWER_TORCH; + } return mLongPressOnPowerBehavior; } @@ -1469,7 +1577,8 @@ public void setScreenshotType(int screenshotType) { @Override public void run() { - mDefaultDisplayPolicy.takeScreenshot(mScreenshotType); + boolean dockMinimized = mWindowManagerInternal.isMinimizedDock(); + mDefaultDisplayPolicy.takeScreenshot(mScreenshotType, dockMinimized); } } @@ -1482,6 +1591,7 @@ public void showGlobalActions() { } void showGlobalActionsInternal(boolean hapticFeedback) { + stopLongshot(); final boolean keyguardShowing = isKeyguardShowingAndNotOccluded(); if (keyguardShowing && !isGlobalActionEnabled()) { return; @@ -1499,6 +1609,30 @@ void showGlobalActionsInternal(boolean hapticFeedback) { mPowerManager.userActivity(SystemClock.uptimeMillis(), false); } + private void stopLongshot() { + ILongScreenshotManager shot = ILongScreenshotManager.Stub.asInterface(ServiceManager.getService(Context.LONGSCREENSHOT_SERVICE)); + if (shot != null) { + try { + if (shot.isLongshotMode()) { + shot.stopLongshot(); + } + } catch (RemoteException e) { + Slog.d(TAG, e.toString()); + } + } + } + + @Override + public void stopLongshotConnection() { + mDefaultDisplayPolicy.stopLongshotConnection(); + } + + @Override + public void takeOPScreenshot(int type) { + mScreenshotRunnable.setScreenshotType(type); + mHandler.post(mScreenshotRunnable); + } + boolean isDeviceProvisioned() { return Settings.Global.getInt( mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0; @@ -1786,6 +1920,7 @@ public void init(Context context, IWindowManager windowManager, mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK); mHasFeatureAuto = mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE); mHasFeatureHdmiCec = mContext.getPackageManager().hasSystemFeature(FEATURE_HDMI_CEC); + mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); mAccessibilityShortcutController = new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId); mLogger = new MetricsLogger(); @@ -2136,6 +2271,9 @@ public void updateSettings() { com.android.internal.R.integer.config_veryLongPressOnPowerBehavior)); mGlobalActionsOnLockDisable = Settings.System.getInt(resolver, Settings.System.LOCK_POWER_MENU_DISABLED, 0) != 0; + mTorchActionMode = Settings.Secure.getIntForUser(resolver, + Settings.Secure.TORCH_POWER_BUTTON_GESTURE, 0, + UserHandle.USER_CURRENT); } if (updateRotation) { updateRotation(true); @@ -4964,6 +5102,18 @@ public void systemReady() { mVrManagerInternal.addPersistentVrModeStateListener(mPersistentVrModeListener); } + mCameraId = ""; + + try { + mCameraId = getCameraId(); + } catch (Throwable e) { + Log.e(TAG, "Couldn't initialize.", e); + } + + if (mCameraManager != null) { + mCameraManager.registerTorchCallback(mTorchCallback, mHandler); + } + readCameraLensCoverState(); updateUiMode(); mDefaultDisplayRotation.updateOrientationListener(); diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index c77ff7ab600..bd2b3948b3a 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -1513,4 +1513,11 @@ default void onDefaultDisplayFocusChangedLw(WindowState newFocus) {} * @return whether the value was changed. */ boolean setAodShowing(boolean aodShowing); + + /** + * Long screenshot + * @hide + */ + public void takeOPScreenshot(int type); + public void stopLongshotConnection(); } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 35e0897f5ae..27dfa9e82fc 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -45,6 +45,7 @@ import android.os.BatteryManagerInternal; import android.os.BatterySaverPolicyConfig; import android.os.Binder; +import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; import android.os.IPowerManager; @@ -102,6 +103,7 @@ import com.android.server.power.batterysaver.BatterySavingStats; import java.io.FileDescriptor; +import java.io.IOException; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -588,6 +590,18 @@ public ProfilePowerState(@UserIdInt int userId, long screenOffTimeout) { } } + // Smart charging + private boolean mSmartChargingEnabled; + private boolean mSmartChargingResetStats; + private boolean mPowerInputSuspended = false; + private int mSmartChargingLevel; + private int mSmartChargingResumeLevel; + private int mSmartChargingLevelDefaultConfig; + private int mSmartChargingResumeLevelDefaultConfig; + private static String mPowerInputSuspendSysfsNode; + private static String mPowerInputSuspendValue; + private static String mPowerInputResumeValue; + /** * All times are in milliseconds. These constants are kept synchronized with the system * global Settings. Any access to this class or its fields should be done while @@ -919,6 +933,19 @@ public void systemReady(IAppOpsService appOps) { resolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.DEVICE_DEMO_MODE), false, mSettingsObserver, UserHandle.USER_SYSTEM); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SMART_CHARGING), + false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SMART_CHARGING_LEVEL), + false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SMART_CHARGING_RESUME_LEVEL), + false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SMART_CHARGING_RESET_STATS), + false, mSettingsObserver, UserHandle.USER_ALL); + IVrManager vrManager = IVrManager.Stub.asInterface(getBinderService(Context.VR_SERVICE)); if (vrManager != null) { try { @@ -990,6 +1017,19 @@ void readConfigurationLocked() { com.android.internal.R.fraction.config_maximumScreenDimRatio, 1, 1); mSupportsDoubleTapWakeConfig = resources.getBoolean( com.android.internal.R.bool.config_supportDoubleTapWake); + // Smart charging + mSmartChargingLevelDefaultConfig = resources.getInteger( + com.android.internal.R.integer.config_smartChargingBatteryLevel); + mSmartChargingResumeLevelDefaultConfig = resources.getInteger( + com.android.internal.R.integer.config_smartChargingBatteryResumeLevel); + mPowerInputSuspendSysfsNode = resources.getString( + com.android.internal.R.string.config_SmartChargingSysfsNode); + mPowerInputSuspendValue = resources.getString( + com.android.internal.R.string.config_SmartChargingSuspendValue); + mPowerInputResumeValue = resources.getString( + com.android.internal.R.string.config_SmartChargingResumeValue); + mSmartChargingResetStats = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.SMART_CHARGING_RESET_STATS, 0) == 1; } private void updateSettingsLocked() { @@ -1018,6 +1058,16 @@ private void updateSettingsLocked() { mTheaterModeEnabled = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0) == 1; mAlwaysOnEnabled = mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT); + mSmartChargingEnabled = Settings.System.getInt(resolver, + Settings.System.SMART_CHARGING, 0) == 1; + mSmartChargingLevel = Settings.System.getInt(resolver, + Settings.System.SMART_CHARGING_LEVEL, + mSmartChargingLevelDefaultConfig); + mSmartChargingResumeLevel = Settings.System.getInt(resolver, + Settings.System.SMART_CHARGING_RESUME_LEVEL, + mSmartChargingResumeLevelDefaultConfig); + mSmartChargingResetStats = Settings.System.getInt(resolver, + Settings.System.SMART_CHARGING_RESET_STATS, 0) == 1; if (mSupportsDoubleTapWakeConfig) { boolean doubleTapWakeEnabled = Settings.Secure.getIntForUser(resolver, @@ -1045,6 +1095,7 @@ private void updateSettingsLocked() { private void handleSettingsChangedLocked() { updateSettingsLocked(); updatePowerStateLocked(); + updateSmartChargingStatus(); } private void acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName, @@ -1804,6 +1855,38 @@ private void updateIsPoweredLocked(int dirty) { } mBatterySaverStateMachine.setBatteryStatus(mIsPowered, mBatteryLevel, mBatteryLevelLow); + updateSmartChargingStatus(); + } + } + + private void updateSmartChargingStatus() { + if (mPowerInputSuspended && (mBatteryLevel <= mSmartChargingResumeLevel) || + (mPowerInputSuspended && !mSmartChargingEnabled)) { + try { + FileUtils.stringToFile(mPowerInputSuspendSysfsNode, mPowerInputResumeValue); + mPowerInputSuspended = false; + } catch (IOException e) { + Slog.e(TAG, "failed to write to " + mPowerInputSuspendSysfsNode); + } + return; + } + + if (mSmartChargingEnabled && !mPowerInputSuspended && (mBatteryLevel >= mSmartChargingLevel)) { + Slog.i(TAG, "Smart charging reset stats: " + mSmartChargingResetStats); + if (mSmartChargingResetStats) { + try { + mBatteryStats.resetStatistics(); + } catch (RemoteException e) { + Slog.e(TAG, "failed to reset battery statistics"); + } + } + + try { + FileUtils.stringToFile(mPowerInputSuspendSysfsNode, mPowerInputSuspendValue); + mPowerInputSuspended = true; + } catch (IOException e) { + Slog.e(TAG, "failed to write to " + mPowerInputSuspendSysfsNode); + } } } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 3907f9561ab..cf39919f7e3 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -622,11 +622,11 @@ public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal } @Override - public void onBiometricAuthenticated(boolean authenticated, String failureReason) { + public void onBiometricAuthenticated(boolean authenticated, String failureReason, boolean requireConfirmation) { enforceBiometricDialog(); if (mBar != null) { try { - mBar.onBiometricAuthenticated(authenticated, failureReason); + mBar.onBiometricAuthenticated(authenticated, failureReason, requireConfirmation); } catch (RemoteException ex) { } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 9656b181379..bfc1b67569e 100755 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -115,6 +115,7 @@ import static com.android.server.wm.ActivityStack.PAUSE_TIMEOUT_MSG; import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE; import static com.android.server.wm.ActivityStack.STOP_TIMEOUT_MSG; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_APPLOCK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_FOCUS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SAVED_STATE; @@ -122,6 +123,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_APPLOCK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_FOCUS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SAVED_STATE; @@ -245,6 +247,7 @@ public final class ActivityRecord extends ConfigurationContainer { private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY; private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; + private static final String TAG_APPLOCK = TAG + POSTFIX_APPLOCK; // TODO(b/67864419): Remove once recents component is overridden private static final String LEGACY_RECENTS_PACKAGE_NAME = "com.android.systemui.recents"; @@ -280,6 +283,7 @@ public final class ActivityRecord extends ConfigurationContainer { final String processName; // process where this component wants to run final String taskAffinity; // as per ActivityInfo.taskAffinity final boolean stateNotNeeded; // As per ActivityInfo.flags + boolean isAppLocked; boolean fullscreen; // The activity is opaque and fills the entire space of this task. // TODO: See if it possible to combine this with the fullscreen field. final boolean hasWallpaper; // Has a wallpaper window as a background. @@ -298,6 +302,7 @@ public final class ActivityRecord extends ConfigurationContainer { private int realTheme; // actual theme resource we will use, never 0. private int windowFlags; // custom window flags for preview window. public int perfActivityBoostHandler = -1; //perflock handler when activity is created. + private int mResizeMode = -1; private TaskRecord task; // the task this is in. private long createTime = System.currentTimeMillis(); long lastVisibleTime; // last time this activity became visible @@ -1033,6 +1038,7 @@ boolean isResolverOrChildActivity() { packageName = aInfo.applicationInfo.packageName; launchMode = aInfo.launchMode; + isAppLocked = mAtmService.isAppLocked(packageName); Entry ent = AttributeCache.instance().get(packageName, realTheme, com.android.internal.R.styleable.Window, mUserId); @@ -1276,6 +1282,16 @@ boolean canLaunchHomeActivity(int uid, ActivityRecord sourceRecord) { return sourceRecord != null && sourceRecord.isResolverOrDelegateActivity(); } + boolean getIsAppLocked() { + isAppLocked = mAtmService.isAppLocked(packageName); + if (isAppLocked) { + info.resizeMode = RESIZE_MODE_UNRESIZEABLE; + } else { + info.resizeMode = mResizeMode; + } + return isAppLocked; + } + /** * @return whether the given package name can launch an assist activity. */ @@ -1308,6 +1324,12 @@ && isHomeIntent(intent) && !isResolverOrDelegateActivity()) { && canLaunchAssistActivity(launchedFromPackage)) { activityType = ACTIVITY_TYPE_ASSISTANT; } + if (mResizeMode == -1){ + mResizeMode = info.resizeMode; + } + if (getIsAppLocked()) { + info.resizeMode = RESIZE_MODE_UNRESIZEABLE; + } setActivityType(activityType); } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 3b3de4b003e..245e51a79b7 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -64,6 +64,7 @@ import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_APPLOCK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_FOCUS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW; @@ -71,6 +72,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_APPLOCK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_FOCUS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS; @@ -144,6 +146,7 @@ class ActivityStarter { private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING; + private static final String TAG_APPLOCK = TAG + POSTFIX_APPLOCK; private static final int INVALID_LAUNCH_MODE = -1; private final ActivityTaskManagerService mService; @@ -771,6 +774,15 @@ private int startActivity(IApplicationThread caller, Intent intent, Intent ephem abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid, callingPackage); + final String pkg = aInfo == null ? null : aInfo.packageName; + if (mService.isAppLocked(pkg) && !mService.isAppOpened(pkg) + && !mService.isAlarmOrCallIntent(intent)) { + Slog.d(TAG_APPLOCK, "Locked pkg:" + pkg + " intent:" + intent); + mService.mAppLockService.setAppIntent(pkg, intent); + mService.mAppLockService.launchBeforeActivity(pkg); + abort = true; + } + boolean restrictedBgActivity = false; if (!abort) { try { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java b/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java index 7f09a071308..864b37cc676 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java @@ -32,7 +32,7 @@ public class ActivityTaskManagerDebugConfig { // While debugging it is sometimes useful to have the category name of the log appended to the // base log tag to make sifting through logs with the same base tag easier. By setting this // constant to true, the category name of the log point will be appended to the log tag. - private static final boolean APPEND_CATEGORY_NAME = false; + private static final boolean APPEND_CATEGORY_NAME = true; // Default log tag for the activities. static final String TAG_ATM = "ActivityTaskManager"; @@ -67,6 +67,7 @@ public class ActivityTaskManagerDebugConfig { static final boolean DEBUG_RESULTS = DEBUG_ALL || false; public static final boolean DEBUG_CLEANUP = DEBUG_ALL || false; public static final boolean DEBUG_METRICS = DEBUG_ALL || false; + public static final boolean DEBUG_APPLOCK = DEBUG_ALL || true; static final String POSTFIX_APP = APPEND_CATEGORY_NAME ? "_App" : ""; static final String POSTFIX_CLEANUP = (APPEND_CATEGORY_NAME) ? "_Cleanup" : ""; @@ -89,4 +90,5 @@ public class ActivityTaskManagerDebugConfig { static final String POSTFIX_TRANSITION = APPEND_CATEGORY_NAME ? "_Transition" : ""; static final String POSTFIX_VISIBILITY = APPEND_CATEGORY_NAME ? "_Visibility" : ""; static final String POSTFIX_RESULTS = APPEND_CATEGORY_NAME ? "_Results" : ""; + public static final String POSTFIX_APPLOCK = APPEND_CATEGORY_NAME ? "_AppLock" : ""; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index c0075ac4c45..712c2fc7be2 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -28,6 +28,7 @@ import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.Manifest.permission.STOP_APP_SWITCHES; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; +import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.ActivityTaskManager.RESIZE_MODE_PRESERVE_WINDOW; @@ -342,6 +343,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { Context mContext; + AppLockService mAppLockService; + /** * This Context is themable and meant for UI display (AlertDialogs, etc.). The theme can * change at runtime. Use mContext for non-UI purposes. @@ -705,6 +708,8 @@ public void onSystemReady() { mRecentTasks.onSystemReadyLocked(); mStackSupervisor.onSystemReady(); } + + mAppLockService = LocalServices.getService(AppLockService.class); } public void onInitPowerManagement() { @@ -1492,7 +1497,16 @@ public void startRecentsActivity(Intent intent, @Deprecated IAssistDataReceiver public final int startActivityFromRecents(int taskId, Bundle bOptions) { enforceCallerIsRecentsOrHasPermission(START_TASKS_FROM_RECENTS, "startActivityFromRecents()"); - + final TaskRecord task = mRootActivityContainer.anyTaskForId(taskId); + final ActivityRecord r = task.getTopActivity(false); + if (r != null) { + if (isAppLocked(r.packageName) && !isAppOpened(r.packageName)) { + mAppLockService.setAppIntent(r.packageName, r.intent); + mAppLockService.setStartingFromRecents(); + mAppLockService.launchBeforeActivity(r.packageName); + return ActivityManager.START_SWITCHES_CANCELED; + } + } final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(bOptions); @@ -1689,6 +1703,15 @@ public final void activityIdle(IBinder token, Configuration config, boolean stop @Override public final void activityResumed(IBinder token) { final long origId = Binder.clearCallingIdentity(); + final ActivityRecord r = ActivityRecord.isInStackLocked(token); + if (r != null) { + if (mAppLockService != null) { + mAppLockService.setForegroundApp(r.packageName); + } + if (isAppLocked(r.packageName)) { + mAppLockService.setAppIntent(r.packageName, r.intent); + } + } synchronized (mGlobalLock) { ActivityRecord.activityResumedLocked(token); mWindowManager.notifyAppResumedFinished(token); @@ -1710,6 +1733,12 @@ public final void activityPaused(IBinder token) { final long origId = Binder.clearCallingIdentity(); synchronized (mGlobalLock) { ActivityStack stack = ActivityRecord.getStackLocked(token); + final ActivityRecord r = ActivityRecord.isInStackLocked(token); + if (r != null) { + if (isAppLocked(r.packageName)) { + mAppLockService.activityStopped(r.packageName, r.intent); + } + } if (stack != null) { stack.activityPausedLocked(token, false); } @@ -3320,6 +3349,9 @@ public void setLockScreenShown(boolean keyguardShowing, boolean aodShowing) { throw new SecurityException("Requires permission " + android.Manifest.permission.DEVICE_POWER); } + if (mAppLockService != null) { + mAppLockService.setKeyguardShown(keyguardShowing); + } synchronized (mGlobalLock) { long ident = Binder.clearCallingIdentity(); @@ -4325,6 +4357,21 @@ public void setSplitScreenResizing(boolean resizing) { } } + boolean isAppLocked(String packageName) { + if (mAppLockService == null || packageName == null) return false; + return mAppLockService.isAppLocked(packageName); + } + + boolean isAppOpened(String packageName) { + if (mAppLockService == null || packageName == null) return true; + return mAppLockService.isAppOpen(packageName); + } + + boolean isAlarmOrCallIntent(Intent intent) { + if (mAppLockService == null) return false; + return mAppLockService.isAlarmOrCallIntent(intent); + } + /** * Check that we have the features required for VR-related API calls, and throw an exception if * not. diff --git a/services/core/java/com/android/server/wm/AppLockService.java b/services/core/java/com/android/server/wm/AppLockService.java new file mode 100644 index 00000000000..3041e333cc3 --- /dev/null +++ b/services/core/java/com/android/server/wm/AppLockService.java @@ -0,0 +1,862 @@ +/** + * Copyright (C) 2017-2020 Paranoid Android + * + * 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.wm; + +import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_APPLOCK; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_APPLOCK; + +import android.app.admin.DevicePolicyManager; +import android.app.ActivityManager; +import android.app.AppGlobals; +import android.app.AppOpsManager; +import android.app.IActivityManager; +import android.app.IAppLockService; +import android.app.IAppLockCallback; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.hardware.biometrics.BiometricConstants; +import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.BiometricPrompt.AuthenticationResult; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.Environment; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.os.BackgroundThread; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.R; +import com.android.server.DisplayThread; +import com.android.server.LocalServices; +import com.android.server.SystemService; + +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.List; + +public class AppLockService extends SystemService { + + private static final String TAG = "AppLockService"; + private static final String TAG_APPLOCK = TAG + POSTFIX_APPLOCK; + + private static final String FILE_NAME = "locked-apps.xml"; + private static final String TAG_LOCKED_APPS = "locked-apps"; + private static final String TAG_PACKAGE = "package"; + private static final String ATTRIBUTE_NAME = "name"; + private static final String ATTRIBUTE_OP_MODE = "opMode"; + private static final String ATTRIBUTE_NOTIFICATION = "notifHide"; + + private final int APPLOCK_TIMEOUT = 15000; + + private AppLockContainer mCurrent; + private PackageManager mPackageManager; + private IPackageManager mIpm; + private AppOpsManager mAppOpsManager; + private CancellationSignal mCancellationSignal; + private BiometricPrompt mBiometricPrompt; + + private UserHandle mUserHandle; + private int mUserId; + private UserManager mUserManager; + private boolean mShowOnlyOnWake; + private boolean mIsSecure; + private boolean mStartingFromRecents; + private boolean mKeyguardShown; + private boolean mLaunchAfterKeyguard; + private boolean mBiometricRunning; + private String mForegroundApp; + + private final LockPatternUtils mLockPatternUtils; + private Context mContext; + + private AtomicFile mFile; + private final AppLockHandler mHandler; + private final Object mLock = new Object(); + + private final ArrayMap mAppsList = new ArrayMap<>(); + private final ArraySet mOpenedApplicationsIndex = new ArraySet<>(); + private final ArraySet mCallbacks= new ArraySet<>(); + + private final BiometricPrompt.AuthenticationCallback mBiometricCallback = + new BiometricPrompt.AuthenticationCallback() { + @Override + public void onAuthenticationError(int errMsgId, CharSequence errString) { + if (DEBUG_APPLOCK && mCurrent != null) Slog.v(TAG, "onAuthenticationError() pkg:" + mCurrent.packageName + + " Id=" + errMsgId + " Name=" + errString); + if (errMsgId == BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED && mStartingFromRecents) { + fallbackToHomeActivity(); + } + mStartingFromRecents = false; + mBiometricRunning = false; + mCurrent = null; + } + + @Override + public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { + if (DEBUG_APPLOCK) Slog.v(TAG, "onAuthenticationHelp"); + if (DEBUG_APPLOCK) Slog.v(TAG, "Help: Id=" + helpMsgId + " Name=" + helpString); + } + + @Override + public void onAuthenticationFailed() { + if (DEBUG_APPLOCK) Slog.v(TAG, "onAuthenticationFailed"); + mStartingFromRecents = false; + mBiometricRunning = false; + } + + @Override + public void onAuthenticationSucceeded(AuthenticationResult result) { + if (DEBUG_APPLOCK) Slog.v(TAG, "onAuthenticationSucceeded result=" + result); + if (mCurrent != null) { + mCurrent.onUnlockSucceed(); + } + mStartingFromRecents = false; + mBiometricRunning = false; + mCurrent = null; + } + }; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction()) + && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "Package removed intent received"); + final Uri data = intent.getData(); + if (data == null) { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, + "Cannot handle package broadcast with null data"); + return; + } + + final String packageName = data.getSchemeSpecificPart(); + removeAppFromList(packageName); + } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "ACTION_SCREEN_OFF"); + clearOpenedAppsList(); + stopBiometricPrompt(); + fallbackToHomeActivity(); + } + } + }; + + public AppLockService(Context context) { + super(context); + + mContext = context; + mHandler = new AppLockHandler(BackgroundThread.getHandler().getLooper()); + mUserId = ActivityManager.getCurrentUser(); + mLockPatternUtils = new LockPatternUtils(context); + + IntentFilter packageFilter = new IntentFilter(); + packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + packageFilter.addDataScheme("package"); + context.registerReceiver(mReceiver, packageFilter); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_SCREEN_OFF); + context.registerReceiver(mReceiver, intentFilter); + } + + @Override + public void onStart() { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "Starting AppLockService"); + publishBinderService(Context.APPLOCK_SERVICE, new AppLockImpl()); + publishLocalService(AppLockService.class, this); + } + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + Slog.v(TAG_APPLOCK, "onBootPhase PHASE_SYSTEM_SERVICES_READY"); + mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + mPackageManager = mContext.getPackageManager(); + mIpm = AppGlobals.getPackageManager(); + } + } + + @Override + public void onUnlockUser(int userHandle) { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "onUnlockUser() mUserId:" + userHandle); + if (!UserManager.get(mContext).isManagedProfile(userHandle)) { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "onUnlockUser() is NOT ManagedProfile"); + mUserId = userHandle; + mHandler.sendEmptyMessage(AppLockHandler.MSG_INIT_APPS); + } + } + + @Override + public void onSwitchUser(int userHandle) { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "onSwitchUser() mUserId:" + userHandle); + if (!UserManager.get(mContext).isManagedProfile(userHandle)) { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "onSwitchUser() is NOT ManagedProfile"); + mUserId = userHandle; + mHandler.sendEmptyMessage(AppLockHandler.MSG_INIT_APPS); + } + } + + @Override + public void onStopUser(int userHandle) { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "onStopUser() userHandle:" + userHandle); + if (mUserId == userHandle) { + mUserId = ActivityManager.getCurrentUser(); + mHandler.sendEmptyMessage(AppLockHandler.MSG_INIT_APPS); + } + } + + private void initLockedApps() { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "initLockedApps(" + mUserId + ")"); + mUserHandle = new UserHandle(mUserId); + mIsSecure = isSecure(); + mFile = new AtomicFile(getFile()); + readState(); + clearOpenedAppsList(); + + mShowOnlyOnWake = Settings.System.getIntForUser(mContext + .getContentResolver(), + Settings.System.APP_LOCK_SHOW_ONLY_ON_WAKE, 0, + mUserId) != 0; + } + + private File getFile() { + File file = new File(Environment.getDataSystemCeDirectory(mUserId), FILE_NAME); + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "getFile(): " + file.getAbsolutePath()); + return file; + } + + private void readState() { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "readState()"); + mAppsList.clear(); + try (FileInputStream in = mFile.openRead()) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parseXml(parser); + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "Read locked-apps.xml successfully"); + } catch (FileNotFoundException e) { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "locked-apps.xml not found"); + Slog.i(TAG, "locked-apps.xml not found"); + } catch (XmlPullParserException | IOException e) { + throw new IllegalStateException("Failed to parse locked-apps.xml: " + mFile, e); + } + } + + private void parseXml(XmlPullParser parser) throws IOException, + XmlPullParserException { + int type; + int depth; + int innerDepth = parser.getDepth() + 1; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { + if (depth > innerDepth || type != XmlPullParser.START_TAG) { + continue; + } + if (parser.getName().equals(TAG_LOCKED_APPS)) { + parsePackages(parser); + return; + } + } + Slog.w(TAG, "Missing <" + TAG_LOCKED_APPS + "> in locked-apps.xml"); + } + + private void parsePackages(XmlPullParser parser) throws IOException, + XmlPullParserException { + int type; + int depth; + int innerDepth = parser.getDepth() + 1; + boolean writeAfter = false; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { + if (depth > innerDepth || type != XmlPullParser.START_TAG) { + continue; + } + if (parser.getName().equals(TAG_PACKAGE)) { + String pkgName = parser.getAttributeValue(null, ATTRIBUTE_NAME); + String appOpMode = parser.getAttributeValue(null, ATTRIBUTE_OP_MODE); + String notifHide = parser.getAttributeValue(null, ATTRIBUTE_NOTIFICATION); + AppLockContainer cont = new AppLockContainer(pkgName, (appOpMode == null) + ? -1 : Integer.parseInt(appOpMode), (notifHide == null) ? false + : Boolean.parseBoolean(notifHide)); + writeAfter = (appOpMode == null) || (Integer.parseInt(appOpMode) == -1); + mAppsList.put(pkgName, cont); + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "parsePackages(): pkgName=" + pkgName + + " appOpMode=" + appOpMode + " notifHide:" + notifHide); + } + } + if (writeAfter) { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "parsePackages(): writeAfter"); + mHandler.sendEmptyMessage(AppLockHandler.MSG_WRITE_STATE); + } + } + + private void writeState() { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "writeState()"); + + FileOutputStream out = null; + try { + out = mFile.startWrite(); + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(out, StandardCharsets.UTF_8.name()); + serializer.setFeature( + "http://xmlpull.org/v1/doc/features.html#indent-output", true); + serializer.startDocument(null, true); + serializeLockedApps(serializer); + serializer.endDocument(); + mFile.finishWrite(out); + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "Wrote locked-apps.xml successfully"); + } catch (IllegalArgumentException | IllegalStateException | IOException e) { + Slog.wtf(TAG, "Failed to write locked-apps.xml, restoring backup", e); + if (out != null) { + mFile.failWrite(out); + } + } finally { + IoUtils.closeQuietly(out); + } + } + + private void serializeLockedApps(XmlSerializer serializer) throws IOException { + serializer.startTag(null, TAG_LOCKED_APPS); + ArrayList apps = new ArrayList<>(mAppsList.values()); + for (AppLockContainer app : apps) { + serializer.startTag(null, TAG_PACKAGE); + serializer.attribute(null, ATTRIBUTE_NAME, app.packageName); + serializer.attribute(null, ATTRIBUTE_OP_MODE, String.valueOf(app.appOpMode)); + serializer.attribute(null, ATTRIBUTE_NOTIFICATION, String.valueOf(app.notifHide)); + serializer.endTag(null, TAG_PACKAGE); + } + serializer.endTag(null, TAG_LOCKED_APPS); + } + + private void addAppToList(String packageName) { + if (DEBUG_APPLOCK) Slog.v(TAG, "addAppToList packageName:" + packageName); + if (!mAppsList.containsKey(packageName)) { + AppLockContainer cont = new AppLockContainer(packageName, -1, false); + mAppsList.put(packageName, cont); + mHandler.sendEmptyMessage(AppLockHandler.MSG_WRITE_STATE); + dispatchCallbacks(packageName); + } + } + + private void removeAppFromList(String packageName) { + if (mAppsList.containsKey(packageName)) { + AppLockContainer cont = getAppLockContainer(packageName); + cont.appRemovedFromList(); + mAppsList.remove(packageName); + mHandler.sendEmptyMessage(AppLockHandler.MSG_WRITE_STATE); + dispatchCallbacks(packageName); + } + } + + private boolean getAppNotificationHide(String packageName) { + AppLockContainer cont = getAppLockContainer(packageName); + if (cont != null) { + return cont.notifHide; + } + return false; + } + + private void setAppNotificationHide(String packageName, boolean hide) { + AppLockContainer cont = getAppLockContainer(packageName); + if (cont != null) { + if (cont.notifHide != hide) { + cont.notifHide = hide; + mHandler.sendEmptyMessage(AppLockHandler.MSG_WRITE_STATE); + dispatchCallbacks(packageName); + } + } + } + + public void reportPasswordChanged(int userId) { + if (mUserId == userId) { + mIsSecure = isSecure(); + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "reportPasswordChanged() mIsSecure:" + mIsSecure); + for (AppLockContainer app : mAppsList.values()) { + app.reportPasswordChanged(); + } + } + } + + public boolean isAppLocked(String packageName) { + if (!mIsSecure) { + return false; + } + return mAppsList.containsKey(packageName); + } + + private AppLockContainer getAppLockContainer(String packageName) { + return mAppsList.get(packageName); + } + + private void clearOpenedAppsList() { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "clearOpenedAppsList()"); + for (String p : mOpenedApplicationsIndex) { + dispatchCallbacks(p); + } + mOpenedApplicationsIndex.clear(); + } + + public boolean isAppOpen(String packageName) { + return mOpenedApplicationsIndex.contains(packageName); + } + + private List getLockedPackages() { + return new ArrayList(mAppsList.keySet()); + } + + public boolean isAlarmOrCallIntent(Intent intent) { + if (intent == null) return false; + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "isAlarmOrCallIntent() intent:" + intent); + + String intentClassName = intent.getComponent().getClassName().toLowerCase(); + return intentClassName.contains("callactivity") + || intentClassName.contains("callingactivity") + || intentClassName.contains("voipactivity") + || intentClassName.contains("alarmactivity"); + } + + void removeOpenedApp(String packageName) { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "removeOpenedApp(" + packageName + ")"); + if (mOpenedApplicationsIndex.remove(packageName)) { + dispatchCallbacks(packageName); + } + } + + void addOpenedApp(String packageName) { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "addOpenedApp(" + packageName + ")"); + if (mOpenedApplicationsIndex.add(packageName)) { + dispatchCallbacks(packageName); + } + } + + public void launchBeforeActivity(String packageName) { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "launchBeforeActivity(" + packageName + ")"); + AppLockContainer cont = getAppLockContainer(packageName); + if (cont != null) { + DisplayThread.getHandler().post(() -> { + mCurrent = cont; + if (mKeyguardShown) { + mLaunchAfterKeyguard = true; + return; + } + mBiometricPrompt = new BiometricPrompt.Builder(mContext) + .setTitle(cont.appLabel) + .setApplockPackage(cont.packageName) + .setDescription(cont.appLabel) + .setDeviceCredentialAllowed(true) + .setConfirmationRequired(false) + .build(); + startBiometricPrompt(); + }); + } + } + + public void activityStopped(String packageName, Intent removed) { + AppLockContainer cont = getAppLockContainer(packageName); + if (cont != null) { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "activityStopped() pkg:" + packageName); + if (isAppOpen(packageName)) { + mHandler.removeMessages(AppLockHandler.MSG_REMOVE_OPENED_APP, cont.packageName); + if (cont.intent.equals(removed)) { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "activityStopped() send: MSG_REMOVE_OPENED_APP"); + final Message msgRemove = mHandler.obtainMessage(AppLockHandler.MSG_REMOVE_OPENED_APP, + cont.packageName); + mHandler.sendMessageDelayed(msgRemove, APPLOCK_TIMEOUT); + } + } + } + } + + public void setAppIntent(String packageName, Intent intent) { + if (DEBUG_APPLOCK) Slog.v(TAG, "setAppIntent(" + packageName + ") intent:" + intent); + AppLockContainer cont = getAppLockContainer(packageName); + if (cont != null) { + cont.intent = intent; + mHandler.removeMessages(AppLockHandler.MSG_REMOVE_OPENED_APP, cont.packageName); + } + } + + public void setStartingFromRecents() { + if (DEBUG_APPLOCK) Slog.v(TAG, "setStartingFromRecents()"); + mStartingFromRecents = true; + } + + public void setForegroundApp(String packageName) { + if (DEBUG_APPLOCK) Slog.v(TAG, "setForegroundApp(" + packageName + ")"); + mForegroundApp = packageName; + } + + private void startBiometricPrompt() { + if (DEBUG_APPLOCK) Slog.v(TAG, "startBiometricPrompt()"); + if (mBiometricRunning) return; + if (mCancellationSignal == null || mCancellationSignal.isCanceled()) { + mCancellationSignal = new CancellationSignal(); + } + mBiometricPrompt.authenticate(mCancellationSignal, mContext.getMainExecutor(), mBiometricCallback); + mBiometricRunning = true; + } + + private void stopBiometricPrompt() { + if (DEBUG_APPLOCK) Slog.v(TAG, "stopBiometricPrompt()"); + if (mCancellationSignal != null) { + mCancellationSignal.cancel(); + } + mBiometricRunning = false; + } + + public void setKeyguardShown(boolean shown) { + mKeyguardShown = shown; + if (mLaunchAfterKeyguard && !shown) { + mLaunchAfterKeyguard = false; + mHandler.postDelayed(() -> { + if (mCurrent != null) { + launchBeforeActivity(mCurrent.packageName); + } + }, 200); + } + } + + private boolean isSecure() { + int storedQuality = mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId); + switch (storedQuality) { + case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: + case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: + case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: + case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: + case DevicePolicyManager.PASSWORD_QUALITY_MANAGED: + return true; + default: + return false; + } + } + + private void fallbackToHomeActivity() { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "fallbackToHomeActivity()"); + if (mAppsList.containsKey(mForegroundApp) || mStartingFromRecents) { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivityAsUser(intent, mUserHandle); + } + } + + private int getLockedAppsCount() { + if (DEBUG_APPLOCK) Slog.v(TAG_APPLOCK, "Number of locked apps: " + mAppsList.size()); + return mIsSecure ? mAppsList.size() : 0; + } + + private void dispatchCallbacks(String packageName) { + mHandler.post(() -> { + synchronized (mCallbacks) { + final int N = mCallbacks.size(); + boolean cleanup = false; + for (int i = 0; i < N; i++) { + final IAppLockCallback callback = mCallbacks.valueAt(i); + try { + if (callback != null) { + callback.onAppStateChanged(packageName); + } else { + cleanup = true; + } + } catch (RemoteException e) { + cleanup = true; + } + } + if (cleanup) { + cleanUpCallbacksLocked(null); + } + } + }); + } + + private void cleanUpCallbacksLocked(IAppLockCallback callback) { + mHandler.post(() -> { + synchronized (mCallbacks) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + IAppLockCallback found = mCallbacks.valueAt(i); + if (found == null || found == callback) { + mCallbacks.remove(i); + } + } + } + }); + } + + private void addAppLockCallback(IAppLockCallback callback) { + mHandler.post(() -> { + synchronized(mCallbacks) { + if (!mCallbacks.contains(callback)) { + mCallbacks.add(callback); + } + } + }); + } + + private void removeAppLockCallback(IAppLockCallback callback) { + mHandler.post(() -> { + synchronized(mCallbacks) { + if (mCallbacks.contains(callback)) { + mCallbacks.remove(callback); + } + } + }); + } + + private void setShowOnlyOnWake(boolean showOnce) { + mShowOnlyOnWake = showOnce; + Settings.System.putIntForUser(mContext + .getContentResolver(), + Settings.System.APP_LOCK_SHOW_ONLY_ON_WAKE, + showOnce ? 1 : 0, + mUserId); + } + + private boolean getShowOnlyOnWake() { + return mShowOnlyOnWake; + } + + private class AppLockImpl extends IAppLockService.Stub { + @Override + public void addAppToList(String packageName) { + AppLockService.this.addAppToList(packageName); + } + + @Override + public void removeAppFromList(String packageName) { + AppLockService.this.removeAppFromList(packageName); + } + + @Override + public boolean isAppLocked(String packageName) { + return AppLockService.this.isAppLocked(packageName); + } + + @Override + public boolean isAppOpen(String packageName) { + return AppLockService.this.isAppOpen(packageName); + } + + @Override + public void setShowOnlyOnWake(boolean showOnce) { + AppLockService.this.setShowOnlyOnWake(showOnce); + } + + @Override + public boolean getShowOnlyOnWake() { + return AppLockService.this.getShowOnlyOnWake(); + } + + @Override + public int getLockedAppsCount() { + return AppLockService.this.getLockedAppsCount(); + } + + @Override + public List getLockedPackages() { + return AppLockService.this.getLockedPackages(); + } + + @Override + public boolean getAppNotificationHide(String packageName) { + return AppLockService.this.getAppNotificationHide(packageName); + } + + @Override + public void setAppNotificationHide(String packageName, boolean hide) { + AppLockService.this.setAppNotificationHide(packageName, hide); + } + + @Override + public void addAppLockCallback(IAppLockCallback callback) { + AppLockService.this.addAppLockCallback(callback); + } + + @Override + public void removeAppLockCallback(IAppLockCallback callback) { + AppLockService.this.removeAppLockCallback(callback); + } + }; + + private class AppLockHandler extends Handler { + + public static final int MSG_INIT_APPS = 0; + public static final int MSG_READ_STATE = 1; + public static final int MSG_WRITE_STATE = 2; + public static final int MSG_REMOVE_OPENED_APP = 3; + + public AppLockHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(android.os.Message msg) { + switch (msg.what) { + case MSG_INIT_APPS: + initLockedApps(); + break; + case MSG_WRITE_STATE: + writeState(); + break; + case MSG_REMOVE_OPENED_APP: + if (!mShowOnlyOnWake) { + removeOpenedApp((String) msg.obj); + } + break; + default: + Slog.w(TAG, "Unknown message:" + msg.what); + } + } + } + + private class AppLockContainer { + private final String packageName; + private ApplicationInfo aInfo; + private CharSequence appLabel; + private int appOpMode = -1; + private Intent intent; + private boolean notifHide; + + public AppLockContainer(String pkg, int opMode, boolean hideNotif) { + packageName = pkg; + notifHide = hideNotif; + + final long ident = Binder.clearCallingIdentity(); + try { + mIpm.setBlockUninstallForUser(packageName, mIsSecure, mUserId); + } catch (RemoteException re) { + // Shouldn't happen. + Slog.e(TAG, "Failed to setBlockUninstallForUser", re); + } finally { + Binder.restoreCallingIdentity(ident); + } + + try { + aInfo = mPackageManager.getApplicationInfo(packageName, 0); + } catch(PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Failed to find package " + packageName, e); + removeAppFromList(packageName); + return; + } + appLabel = mPackageManager.getApplicationLabel(aInfo); + + if (opMode == -1) { + appOpMode = mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, + aInfo.uid, packageName); + } else { + appOpMode = opMode; + } + + if (appOpMode == AppOpsManager.MODE_ALLOWED + || appOpMode == AppOpsManager.MODE_DEFAULT) { + mAppOpsManager.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, + aInfo.uid, packageName, AppOpsManager.MODE_ERRORED); + } + } + + private void startActivityAfterUnlock() { + if (DEBUG_APPLOCK) Slog.v(TAG, "startActivityAfterUnlock() intent:" + intent); + Intent fallBackIntent = new Intent(Intent.ACTION_MAIN); + fallBackIntent.setPackage(packageName); + List resolveInfos = mPackageManager.queryIntentActivities(fallBackIntent, 0); + if(resolveInfos.size() > 0) { + ResolveInfo launchable = resolveInfos.get(0); + ActivityInfo activity = launchable.activityInfo; + ComponentName name = new ComponentName(activity.applicationInfo.packageName, + activity.name); + fallBackIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + fallBackIntent.setComponent(name); + } + if (intent != null) { + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivityAsUser(intent, mUserHandle); + } else if (fallBackIntent != null) { + mContext.startActivityAsUser(fallBackIntent, mUserHandle); + } + if (fallBackIntent != null) { + intent = fallBackIntent; + } + } + + private void onUnlockSucceed() { + addOpenedApp(packageName); + startActivityAfterUnlock(); + } + + private void appRemovedFromList() { + Slog.d(TAG, "appRemovedFromList() appOpMode: " + appOpMode); + + final long ident = Binder.clearCallingIdentity(); + try { + mIpm.setBlockUninstallForUser(packageName, false, mUserId); + } catch (RemoteException re) { + // Shouldn't happen. + Slog.e(TAG, "Failed to setBlockUninstallForUser", re); + } finally { + Binder.restoreCallingIdentity(ident); + } + + mAppOpsManager.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, + aInfo.uid, packageName, appOpMode); + } + + private void reportPasswordChanged() { + final long ident = Binder.clearCallingIdentity(); + try { + mIpm.setBlockUninstallForUser(packageName, mIsSecure, mUserId); + } catch (RemoteException re) { + // Shouldn't happen. + Slog.e(TAG, "Failed to setBlockUninstallForUser", re); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } +} diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index f1a9e60d18a..3a84230b10e 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -2459,7 +2459,7 @@ boolean canTurnScreenOn() { */ boolean shouldUseAppThemeSnapshot() { return mDisablePreviewScreenshots || forAllWindows(w -> (w.mAttrs.flags & FLAG_SECURE) != 0, - true /* topToBottom */); + true /* topToBottom */) || mActivityRecord.getIsAppLocked(); } SurfaceControl getAppAnimationLayer() { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 341645c2326..22d12e6432f 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -130,14 +130,16 @@ import android.graphics.Region; import android.hardware.input.InputManager; import android.hardware.power.V1_0.PowerHint; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; -import android.util.BoostFramework; +import android.provider.Settings; import android.util.ArraySet; +import android.util.BoostFramework; import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Slog; @@ -400,6 +402,8 @@ public void onBarVisibilityChanged(boolean visible) { private RefreshRatePolicy mRefreshRatePolicy; + private int mDisplayRotation; + // -------- PolicyHandler -------- private static final int MSG_UPDATE_DREAMING_SLEEP_TOKEN = 1; private static final int MSG_REQUEST_TRANSIENT_BARS = 2; @@ -1582,6 +1586,7 @@ public void onInputEvent(InputEvent event) { */ public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) { displayFrames.onBeginLayout(); + mDisplayRotation = displayFrames.mRotation; mSystemGestures.screenWidth = displayFrames.mUnrestricted.width(); mSystemGestures.screenHeight = displayFrames.mUnrestricted.height(); @@ -3772,16 +3777,51 @@ public void onLockTaskStateChangedLw(int lockTaskState) { * @param screenshotType The type of screenshot, for example either * {@link WindowManager#TAKE_SCREENSHOT_FULLSCREEN} or * {@link WindowManager#TAKE_SCREENSHOT_SELECTED_REGION} + * @param dockMinimized Whether the Dock is minimized */ - public void takeScreenshot(int screenshotType) { + public void takeScreenshot(int screenshotType, boolean dockMinimized) { if (mScreenshotHelper != null) { + boolean longshot; + boolean inMultiWindow = mFocusedWindow != null + ? mFocusedWindow.inMultiWindowMode() + : false; + if (screenshotType == WindowManager.TAKE_SCREENSHOT_SELECTED_REGION + || mService.mPolicy.isKeyguardOccluded() + || !mService.mPolicy.isUserSetupComplete() + || !isDeviceProvisioned() + || ((inMultiWindow && !dockMinimized) || mDisplayRotation != 0)) { + longshot = false; + } else { + longshot = true; + } + Bundle screenshotBundle = new Bundle(); + screenshotBundle.putBoolean("longshot", longshot); + if (mFocusedWindow != null) { + screenshotBundle.putString("focusWindow", mFocusedWindow.getAttrs().packageName); + } + if (mFocusedWindow != null + && (mFocusedWindow.getAttrs().flags & WindowManager.LayoutParams.FLAG_SECURE) != 0) { + mScreenshotHelper.notifyScreenshotCaptureError(); + return; + } mScreenshotHelper.takeScreenshot(screenshotType, mStatusBar != null && mStatusBar.isVisibleLw(), mNavigationBar != null && mNavigationBar.isVisibleLw(), - mHandler, null /* completionConsumer */); + mHandler, longshot, screenshotBundle); } } + public void stopLongshotConnection() { + if (mScreenshotHelper != null) { + mScreenshotHelper.stopLongshotConnection(); + } + } + + private boolean isDeviceProvisioned() { + return Settings.Global.getInt( + mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0; + } + RefreshRatePolicy getRefreshRatePolicy() { return mRefreshRatePolicy; } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index b407ac5e60c..ce2a10e4eb7 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -505,4 +505,8 @@ public abstract void setOnHardKeyboardStatusChangeListener( */ public abstract void removeNonHighRefreshRatePackage(@NonNull String packageName); + /** + * Long screenshot + */ + public abstract boolean isMinimizedDock(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index f4f6d25577b..062dcb428d8 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7591,6 +7591,20 @@ public void removeNonHighRefreshRatePackage(@NonNull String packageName) { .removeNonHighRefreshRatePackage(packageName)); } } + + @Override + public boolean isMinimizedDock() { + boolean isMinimizedDock; + synchronized (mWindowMap) { + try { + boostPriorityForLockedSection(); + isMinimizedDock = getDefaultDisplayContentLocked().getDockedDividerController().isMinimizedDock(); + } finally { + resetPriorityAfterLockedSection(); + } + } + return isMinimizedDock; + } } void registerAppFreezeListener(AppFreezeListener listener) { @@ -7867,4 +7881,14 @@ private void handleDisplayFocusChange(WindowState window) { 0 /* configChanges */, !PRESERVE_WINDOWS, true /* notifyClients */); } } + + @Override + public void stopLongshotConnection() { + mPolicy.stopLongshotConnection(); + } + + @Override + public void takeOPScreenshot(int type) { + mPolicy.takeOPScreenshot(type); + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index a61d2b1dc35..c4330d595e1 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -72,6 +72,7 @@ import android.view.inputmethod.InputMethodSystemProperty; import com.android.internal.R; +import com.android.internal.custom.longshot.LongScreenshotManagerService; import com.android.internal.logging.MetricsLogger; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BinderInternal; @@ -156,6 +157,7 @@ import com.android.server.vr.VrManagerService; import com.android.server.webkit.WebViewUpdateService; import com.android.server.wm.ActivityTaskManagerService; +import com.android.server.wm.AppLockService; import com.android.server.wm.WindowManagerGlobalLock; import com.android.server.wm.WindowManagerService; @@ -1098,9 +1100,17 @@ private void startOtherServices() { mSystemServiceManager.startService(ActivityTriggerService.class); traceEnd(); + traceBeginAndSlog("StartAppLockService"); + mSystemServiceManager.startService(AppLockService.class); + traceEnd(); + traceBeginAndSlog("SignedConfigService"); SignedConfigService.registerUpdateReceiver(mSystemContext); traceEnd(); + + traceBeginAndSlog("LongScreenshotManagerService"); + ServiceManager.addService(Context.LONGSCREENSHOT_SERVICE, LongScreenshotManagerService.getInstance(context)); + traceEnd(); } catch (RuntimeException e) { Slog.e("System", "******************************************"); Slog.e("System", "************ Failure starting core service", e);