diff --git a/Android.bp b/Android.bp index 8c0158549d2d..6ada7ca684e9 100644 --- a/Android.bp +++ b/Android.bp @@ -142,6 +142,8 @@ filegroup { ":vold_aidl", ":deviceproductinfoconstants_aidl", + ":adbrootservice_aidl", + // For the generated R.java and Manifest.java ":framework-res{.aapt.srcjar}", @@ -265,6 +267,11 @@ java_library { "com.android.sysprop.localization", "PlatformProperties", + "vendor.lineage.livedisplay-V2.0-java", + "vendor.lineage.livedisplay-V2.1-java", + "vendor.lineage.touch-V1.0-java", + "vendor.lineage.health-V1-java", + "SurfaceFlingerProperties", ], sdk_version: "core_platform", installable: false, diff --git a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java index 1fc888b06ffd..dfd752075d11 100644 --- a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java @@ -74,6 +74,8 @@ void addPowerSaveTempWhitelistAppDirect(int uid, long duration, boolean isAppOnWhitelist(int appid); + int[] getPowerSaveWhitelistSystemAppIds(); + int[] getPowerSaveWhitelistUserAppIds(); int[] getPowerSaveTempWhitelistAppIds(); diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java index 33f6899239c6..cb83966278bb 100644 --- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java +++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java @@ -111,6 +111,12 @@ public class AppStateTrackerImpl implements AppStateTracker { @GuardedBy("mLock") final SparseBooleanArray mActiveUids = new SparseBooleanArray(); + /** + * System exemption list in the device idle controller. + */ + @GuardedBy("mLock") + private int[] mPowerExemptSystemAppIds = new int[0]; + /** * System except-idle + user exemption list in the device idle controller. */ @@ -1041,6 +1047,7 @@ private void cleanUpArrayForUser(SparseBooleanArray array, int removedUserId) { * Called by device idle controller to update the power save exemption lists. */ public void setPowerSaveExemptionListAppIds( + int[] powerSaveExemptionListSystemAppIdArray, int[] powerSaveExemptionListExceptIdleAppIdArray, int[] powerSaveExemptionListUserAppIdArray, int[] tempExemptionListAppIdArray) { @@ -1048,6 +1055,7 @@ public void setPowerSaveExemptionListAppIds( final int[] previousExemptionList = mPowerExemptAllAppIds; final int[] previousTempExemptionList = mTempExemptAppIds; + mPowerExemptSystemAppIds = powerSaveExemptionListSystemAppIdArray; mPowerExemptAllAppIds = powerSaveExemptionListExceptIdleAppIdArray; mTempExemptAppIds = tempExemptionListAppIdArray; mPowerExemptUserAppIds = powerSaveExemptionListUserAppIdArray; @@ -1265,6 +1273,18 @@ public boolean isUidPowerSaveUserExempt(int uid) { } } + /** + * @return whether or not a UID is in either the user defined power-save exemption list or the + system full exemption list (not including except-idle) + */ + public boolean isUidPowerSaveIdleExempt(int uid) { + final int appId = UserHandle.getAppId(uid); + synchronized (mLock) { + return ArrayUtils.contains(mPowerExemptUserAppIds, appId) + || ArrayUtils.contains(mPowerExemptSystemAppIds, appId); + } + } + /** * @return whether a UID is in the temp power-save exemption list or not. * @@ -1300,6 +1320,9 @@ public void dump(IndentingPrintWriter pw) { pw.print("Active uids: "); dumpUids(pw, mActiveUids); + pw.print("System exemption list appids: "); + pw.println(Arrays.toString(mPowerExemptSystemAppIds)); + pw.print("Except-idle + user exemption list appids: "); pw.println(Arrays.toString(mPowerExemptAllAppIds)); @@ -1375,6 +1398,10 @@ public void dumpProto(ProtoOutputStream proto, long fieldId) { } } + for (int appId : mPowerExemptSystemAppIds) { + proto.write(AppStateTrackerProto.POWER_SAVE_SYSTEM_EXEMPT_APP_IDS, appId); + } + for (int appId : mPowerExemptAllAppIds) { proto.write(AppStateTrackerProto.POWER_SAVE_EXEMPT_APP_IDS, appId); } diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index c1894f0f795f..1c39344bcd07 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -580,6 +580,12 @@ static String lightStateToString(int state) { */ private final SparseBooleanArray mPowerSaveWhitelistSystemAppIds = new SparseBooleanArray(); + /** + * Current system app IDs that are in the complete power save white list. This array can + * be shared with others because it will not be modified once set. + */ + private int[] mPowerSaveWhitelistSystemAppIdArray = new int[0]; + /** * App IDs that have been white-listed to opt out of power save restrictions, except * for device idle modes. @@ -2382,6 +2388,11 @@ public String[] getFullPowerWhitelistExceptIdle() { return DeviceIdleController.this.getFullPowerWhitelistInternalUnchecked(); } + @Override + public int[] getPowerSaveWhitelistSystemAppIds() { + return DeviceIdleController.this.getPowerSaveWhitelistSystemAppIds(); + } + /** * Returns the array of app ids whitelisted by user. Take care not to * modify this, as it is a reference to the original copy. But the reference @@ -2607,6 +2618,12 @@ boolean isAppOnWhitelistInternal(int appid) { } } + int[] getPowerSaveWhitelistSystemAppIds() { + synchronized (this) { + return mPowerSaveWhitelistSystemAppIdArray; + } + } + int[] getPowerSaveWhitelistUserAppIds() { synchronized (this) { return mPowerSaveWhitelistUserAppIdArray; @@ -2617,6 +2634,16 @@ private static File getSystemDir() { return new File(Environment.getDataDirectory(), "system"); } + /** Returns the keys of a SparseBooleanArray, paying no attention to its values. */ + private static int[] keysToIntArray(final SparseBooleanArray sparseArray) { + final int size = sparseArray.size(); + final int[] array = new int[size]; + for (int i = 0; i < size; i++) { + array[i] = sparseArray.keyAt(i); + } + return array; + } + @Override public void onStart() { final PackageManager pm = getContext().getPackageManager(); @@ -2663,6 +2690,7 @@ public void onStart() { } catch (PackageManager.NameNotFoundException e) { } } + mPowerSaveWhitelistSystemAppIdArray = keysToIntArray(mPowerSaveWhitelistSystemAppIds); mConstants = mInjector.getConstants(this, mHandler, getContext().getContentResolver()); @@ -4464,6 +4492,7 @@ private void reportTempWhitelistChangedLocked(final int uid, final boolean added private void passWhiteListsToForceAppStandbyTrackerLocked() { mAppStateTracker.setPowerSaveExemptionListAppIds( + mPowerSaveWhitelistSystemAppIdArray, mPowerSaveWhitelistExceptIdleAppIdArray, mPowerSaveWhitelistUserAppIdArray, mTempWhitelistAppIdArray); diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 0f3b1c366fb0..87d2a2858e5c 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -2739,7 +2739,7 @@ public void set(String callingPackage, } else if (workSource == null && (UserHandle.isCore(callingUid) || UserHandle.isSameApp(callingUid, mSystemUiUid) || ((mAppStateTracker != null) - && mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) { + && mAppStateTracker.isUidPowerSaveIdleExempt(callingUid)))) { flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; flags &= ~(FLAG_ALLOW_WHILE_IDLE | FLAG_PRIORITIZE); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java index d5c9ae615486..b91d8f2cac44 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java @@ -75,6 +75,7 @@ public final class DeviceIdleJobsController extends StateController { * True when in device idle mode, so we don't want to schedule any jobs. */ private boolean mDeviceIdleMode; + private int[] mPowerSaveWhitelistSystemAppIds; private int[] mDeviceIdleWhitelistAppIds; private int[] mPowerSaveTempWhitelistAppIds; @@ -134,6 +135,8 @@ public DeviceIdleJobsController(JobSchedulerService service) { mLocalDeviceIdleController = LocalServices.getService(DeviceIdleInternal.class); mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds(); + mPowerSaveWhitelistSystemAppIds = + mLocalDeviceIdleController.getPowerSaveWhitelistSystemAppIds(); mPowerSaveTempWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds(); mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor(); @@ -197,8 +200,9 @@ public void setUidActiveLocked(int uid, boolean active) { * Checks if the given job's scheduling app id exists in the device idle user whitelist. */ boolean isWhitelistedLocked(JobStatus job) { - return Arrays.binarySearch(mDeviceIdleWhitelistAppIds, - UserHandle.getAppId(job.getSourceUid())) >= 0; + final int appId = UserHandle.getAppId(job.getSourceUid()); + return Arrays.binarySearch(mDeviceIdleWhitelistAppIds, appId) >= 0 + || Arrays.binarySearch(mPowerSaveWhitelistSystemAppIds, appId) >= 0; } /** diff --git a/core/api/current.txt b/core/api/current.txt index f817241d80da..e87003facd9d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -37620,6 +37620,7 @@ package android.provider { field public static final String SELECTED_INPUT_METHOD_SUBTYPE = "selected_input_method_subtype"; field public static final String SETTINGS_CLASSNAME = "settings_classname"; field public static final String SKIP_FIRST_USE_HINTS = "skip_first_use_hints"; + field public static final String TETHERING_ALLOW_VPN_UPSTREAMS = "tethering_allow_vpn_upstreams"; field public static final String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled"; field @Deprecated public static final String TTS_DEFAULT_COUNTRY = "tts_default_country"; field @Deprecated public static final String TTS_DEFAULT_LANG = "tts_default_lang"; diff --git a/core/java/android/adb/ADBRootService.java b/core/java/android/adb/ADBRootService.java new file mode 100644 index 000000000000..52610eadad71 --- /dev/null +++ b/core/java/android/adb/ADBRootService.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.adb; + +import android.adbroot.IADBRootService; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Slog; + +/** + * {@hide} + */ +public class ADBRootService { + private static final String TAG = "ADBRootService"; + + private static final String ADB_ROOT_SERVICE = "adbroot_service"; + + private IADBRootService mService; + + private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + if (mService != null) { + mService.asBinder().unlinkToDeath(this, 0); + } + mService = null; + } + }; + + private synchronized IADBRootService getService() + throws RemoteException { + if (mService != null) { + return mService; + } + + final IBinder service = ServiceManager.getService(ADB_ROOT_SERVICE); + if (service != null) { + service.linkToDeath(mDeathRecipient, 0); + mService = IADBRootService.Stub.asInterface(service); + return mService; + } + + Slog.e(TAG, "Unable to acquire ADBRootService"); + return null; + } + + /** + * @hide + */ + public boolean isSupported() { + try { + final IADBRootService svc = getService(); + if (svc != null) { + return svc.isSupported(); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return false; + } + + /** + * @hide + */ + public void setEnabled(boolean enable) { + try { + final IADBRootService svc = getService(); + if (svc != null) { + svc.setEnabled(enable); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public boolean getEnabled() { + try { + final IADBRootService svc = getService(); + if (svc != null) { + return svc.getEnabled(); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return false; + } +} diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index ed6b85125e66..f01a9173389b 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -132,6 +132,7 @@ import com.android.internal.annotations.Immutable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; +import com.android.internal.util.PropImitationHooks; import com.android.internal.util.UserIcons; import dalvik.system.VMRuntime; @@ -832,7 +833,8 @@ public Boolean recompute(HasSystemFeatureQuery query) { @Override public boolean hasSystemFeature(String name, int version) { - return mHasSystemFeatureCache.query(new HasSystemFeatureQuery(name, version)); + return PropImitationHooks.hasSystemFeature(name, + mHasSystemFeatureCache.query(new HasSystemFeatureQuery(name, version))); } /** @hide */ diff --git a/core/java/android/app/ConfigurationController.java b/core/java/android/app/ConfigurationController.java index 62a50dbbd6f7..5daec472ea07 100644 --- a/core/java/android/app/ConfigurationController.java +++ b/core/java/android/app/ConfigurationController.java @@ -29,6 +29,9 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.HardwareRenderer; +import android.graphics.Typeface; +import android.inputmethodservice.InputMethodService; +import android.os.Build; import android.os.LocaleList; import android.os.Trace; import android.util.DisplayMetrics; @@ -198,6 +201,7 @@ private void handleConfigurationChangedInner(@Nullable Configuration config, final Application app = mActivityThread.getApplication(); final Resources appResources = app.getResources(); + Typeface.updateDefaultFont(appResources); mResourcesManager.applyConfigurationToResources(config, compat); updateLocaleListFromAppContext(app.getApplicationContext()); diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java index b781ce50c4db..de0244f3934f 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -301,6 +301,13 @@ public class DownloadManager { */ public final static int PAUSED_UNKNOWN = 4; + /** + * Value of {@link #COLUMN_REASON} when the download is paused manually. + * + * @hide + */ + public final static int PAUSED_MANUAL = 5; + /** * Broadcast intent action sent by the download manager when a download completes. */ @@ -995,6 +1002,7 @@ Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) { parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_TO_RETRY)); parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_FOR_NETWORK)); parts.add(statusClause("=", Downloads.Impl.STATUS_QUEUED_FOR_WIFI)); + parts.add(statusClause("=", Downloads.Impl.STATUS_PAUSED_MANUAL)); } if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) { parts.add(statusClause("=", Downloads.Impl.STATUS_SUCCESS)); @@ -1289,6 +1297,34 @@ public void forceDownload(long... ids) { mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids)); } + /** + * Pause the given running download manually. + * + * @param id the ID of the download to be paused + * @return the number of downloads actually updated + * @hide + */ + public int pauseDownload(long id) { + ContentValues values = new ContentValues(); + values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PAUSED_MANUAL); + + return mResolver.update(ContentUris.withAppendedId(mBaseUri, id), values, null, null); + } + + /** + * Resume the given paused download manually. + * + * @param id the ID of the download to be resumed + * @return the number of downloads actually updated + * @hide + */ + public int resumeDownload(long id) { + ContentValues values = new ContentValues(); + values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_RUNNING); + + return mResolver.update(ContentUris.withAppendedId(mBaseUri, id), values, null, null); + } + /** * Returns maximum size, in bytes, of downloads that may go over a mobile connection; or null if * there's no limit @@ -1779,6 +1815,9 @@ private long getPausedReason(int status) { case Downloads.Impl.STATUS_QUEUED_FOR_WIFI: return PAUSED_QUEUED_FOR_WIFI; + case Downloads.Impl.STATUS_PAUSED_MANUAL: + return PAUSED_MANUAL; + default: return PAUSED_UNKNOWN; } @@ -1834,6 +1873,7 @@ private int translateStatus(int status) { case Downloads.Impl.STATUS_WAITING_TO_RETRY: case Downloads.Impl.STATUS_WAITING_FOR_NETWORK: case Downloads.Impl.STATUS_QUEUED_FOR_WIFI: + case Downloads.Impl.STATUS_PAUSED_MANUAL: return STATUS_PAUSED; case Downloads.Impl.STATUS_SUCCESS: diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index ffb920b907ab..0a6f9228f820 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -964,6 +964,21 @@ interface IActivityManager { int checkPermissionForDevice(in String permission, int pid, int uid, int deviceId); + /** + * Should disable touch if three fingers to screen shot is active? + */ + boolean isSwipeToScreenshotGestureActive(); + + /** + * Set whether three fingers to screen shot is active. + */ + void setSwipeToScreenshotGestureActive(boolean enabled); + + /** + * Force full screen for devices with cutout + */ + boolean shouldForceCutoutFullscreen(in String packageName); + /** * Notify AMS about binder transactions to frozen apps. * diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index b9fe356690e0..74c56007bbf8 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -82,6 +82,8 @@ interface INotificationManager boolean areNotificationsEnabled(String pkg); int getPackageImportance(String pkg); boolean isImportanceLocked(String pkg, int uid); + void setNotificationSoundTimeout(String pkg, int uid, long timeout); + long getNotificationSoundTimeout(String pkg, int uid); List getAllowedAssistantAdjustments(String pkg); diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 93a9489849af..9c0490795f39 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -62,6 +62,7 @@ import android.view.WindowManagerGlobal; import com.android.internal.content.ReferrerIntent; +import com.android.internal.util.PropImitationHooks; import java.io.File; import java.lang.annotation.Retention; @@ -1353,6 +1354,7 @@ public Application newApplication(ClassLoader cl, String className, Context cont Application app = getFactory(context.getPackageName()) .instantiateApplication(cl, className); app.attach(context); + PropImitationHooks.setProps(context); return app; } @@ -1370,6 +1372,7 @@ static public Application newApplication(Class clazz, Context context) ClassNotFoundException { Application app = (Application)clazz.newInstance(); app.attach(context); + PropImitationHooks.setProps(context); return app; } diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 62820ad5a4d6..2587a5adce90 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -1305,9 +1305,11 @@ private LockscreenCredential createLockscreenCredential( CharSequence pinStr = new String(password); return LockscreenCredential.createPin(pinStr); case PATTERN: + byte patternSize = new LockPatternUtils(mContext).getLockPatternSize( + mContext.getUserId()); List pattern = - LockPatternUtils.byteArrayToPattern(password); - return LockscreenCredential.createPattern(pattern); + LockPatternUtils.byteArrayToPattern(password, patternSize); + return LockscreenCredential.createPattern(pattern, patternSize); default: throw new IllegalArgumentException("Unknown lock type " + lockType); } diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 84a4eb4acddc..9d7a165ce72f 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -174,22 +174,58 @@ public void registerResourcePaths(@NonNull String uniqueId, @NonNull Application } /** - * Apply the registered library paths to the passed impl object - * @return the hash code for the current version of the registered paths + * Apply the registered library paths to the passed AssetManager. If may create a new + * AssetManager if any changes are needed and it isn't allowed to reuse the old one. + * + * @return new AssetManager and the hash code for the current version of the registered paths */ - public int updateResourceImplWithRegisteredLibs(@NonNull ResourcesImpl impl) { + public @NonNull Pair updateResourceImplAssetsWithRegisteredLibs( + @NonNull AssetManager assets, boolean reuseAssets) { if (!Flags.registerResourcePaths()) { - return 0; + return new Pair<>(assets, 0); } - final var collector = new PathCollector(null); - final int size = mSharedLibAssetsMap.size(); - for (int i = 0; i < size; i++) { - final var libraryKey = mSharedLibAssetsMap.valueAt(i).getResourcesKey(); - collector.appendKey(libraryKey); + final int size; + final PathCollector collector; + + synchronized (mLock) { + size = mSharedLibAssetsMap.size(); + if (assets == AssetManager.getSystem()) { + return new Pair<>(assets, size); + } + collector = new PathCollector(resourcesKeyFromAssets(assets)); + for (int i = 0; i < size; i++) { + final var libraryKey = mSharedLibAssetsMap.valueAt(i).getResourcesKey(); + collector.appendKey(libraryKey); + } } - impl.getAssets().addPresetApkKeys(extractApkKeys(collector.collectedKey())); - return size; + if (collector.isSameAsOriginal()) { + return new Pair<>(assets, size); + } + if (reuseAssets) { + assets.addPresetApkKeys(extractApkKeys(collector.collectedKey())); + return new Pair<>(assets, size); + } + final var newAssetsBuilder = new AssetManager.Builder().setNoInit(); + for (final var asset : assets.getApkAssets()) { + // Skip everything that's either default, or will get added by the collector (builder + // doesn't check for duplicates at all). + if (asset.isSystem() || asset.isForLoader() || asset.isOverlay() + || asset.isSharedLib()) { + continue; + } + newAssetsBuilder.addApkAssets(asset); + } + for (final var key : extractApkKeys(collector.collectedKey())) { + try { + final var asset = loadApkAssets(key); + newAssetsBuilder.addApkAssets(asset); + } catch (IOException e) { + Log.e(TAG, "Couldn't load assets for key " + key, e); + } + } + assets.getLoaders().forEach(newAssetsBuilder::addLoader); + return new Pair<>(newAssetsBuilder.build(), size); } public static class ApkKey { @@ -621,6 +657,23 @@ private static String overlayPathToIdmapPath(String path) { return apkKeys; } + private ResourcesKey resourcesKeyFromAssets(@NonNull AssetManager assets) { + final var libs = new ArrayList(); + final var overlays = new ArrayList(); + for (final ApkAssets asset : assets.getApkAssets()) { + if (asset.isSystem() || asset.isForLoader()) { + continue; + } + if (asset.isOverlay()) { + overlays.add(asset.getAssetPath()); + } else if (asset.isSharedLib()) { + libs.add(asset.getAssetPath()); + } + } + return new ResourcesKey(null, null, overlays.toArray(new String[0]), + libs.toArray(new String[0]), 0, null, null); + } + /** * Creates an AssetManager from the paths within the ResourcesKey. * @@ -749,7 +802,7 @@ private int generateDisplayId(@NonNull ResourcesKey key) { final Configuration config = generateConfig(key); final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj); - final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj); + final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj, true); if (DEBUG) { Slog.d(TAG, "- creating impl=" + impl + " with key: " + key); @@ -1832,31 +1885,32 @@ private void redirectAllResourcesToNewImplLocked( for (int i = 0; i < resourcesCount; i++) { final WeakReference ref = mAllResourceReferences.get(i); final Resources r = ref != null ? ref.get() : null; - if (r != null) { - final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); - if (key != null) { - final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key); - if (impl == null) { - throw new Resources.NotFoundException("failed to redirect ResourcesImpl"); - } - r.setImpl(impl); - } else { - // ResourcesKey is null which means the ResourcesImpl could belong to a - // Resources created by application through Resources constructor and was not - // managed by ResourcesManager, so the ResourcesImpl needs to be recreated to - // have shared library asset paths appended if there are any. - if (r.getImpl() != null) { - final ResourcesImpl oldImpl = r.getImpl(); - final AssetManager oldAssets = oldImpl.getAssets(); - // ResourcesImpl constructor will help to append shared library asset paths. - if (oldAssets != AssetManager.getSystem() && oldAssets.isUpToDate()) { - final ResourcesImpl newImpl = new ResourcesImpl(oldAssets, - oldImpl.getMetrics(), oldImpl.getConfiguration(), - oldImpl.getDisplayAdjustments()); + if (r == null) { + continue; + } + final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); + if (key != null) { + final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key); + if (impl == null) { + throw new Resources.NotFoundException("failed to redirect ResourcesImpl"); + } + r.setImpl(impl); + } else { + // ResourcesKey is null which means the ResourcesImpl could belong to a + // Resources created by application through Resources constructor and was not + // managed by ResourcesManager, so the ResourcesImpl needs to be recreated to + // have shared library asset paths appended if there are any. + final ResourcesImpl oldImpl = r.getImpl(); + if (oldImpl != null) { + final AssetManager oldAssets = oldImpl.getAssets(); + // ResourcesImpl constructor will help to append shared library asset paths. + if (oldAssets != AssetManager.getSystem()) { + if (oldAssets.isUpToDate()) { + final ResourcesImpl newImpl = new ResourcesImpl(oldImpl); r.setImpl(newImpl); } else { - Slog.w(TAG, "Skip appending shared library asset paths for the " - + "Resource as its assets are not up to date."); + Slog.w(TAG, "Skip appending shared library asset paths for " + + "the Resources as its assets are not up to date."); } } } diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 63e039143917..cc1e330bdfe2 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -239,6 +239,8 @@ public class StatusBarManager { public static final int CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = 2; /** @hide */ public static final int CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE = 3; + /** @hide */ + public static final int CAMERA_LAUNCH_SOURCE_SCREEN_GESTURE = 4; /** * Broadcast action: sent to apps that hold the status bar permission when diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 03bec71548a8..d041573a90ee 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -245,7 +245,6 @@ import android.telephony.TelephonyFrameworkInitializer; import android.telephony.TelephonyRegistryManager; import android.transparency.BinaryTransparencyManager; -import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import android.uwb.UwbFrameworkInitializer; @@ -280,6 +279,7 @@ import com.android.internal.policy.PhoneLayoutInflater; import com.android.internal.util.Preconditions; +import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -321,10 +321,10 @@ public final class SystemServiceRegistry { // Service registry information. // This information is never changed once static initialization has completed. private static final Map, String> SYSTEM_SERVICE_NAMES = - new ArrayMap, String>(); + new HashMap, String>(); private static final Map> SYSTEM_SERVICE_FETCHERS = - new ArrayMap>(); - private static final Map SYSTEM_SERVICE_CLASS_NAMES = new ArrayMap<>(); + new HashMap>(); + private static final Map SYSTEM_SERVICE_CLASS_NAMES = new HashMap<>(); private static int sServiceCacheSize; diff --git a/core/java/android/app/admin/SystemUpdateInfo.java b/core/java/android/app/admin/SystemUpdateInfo.java index 9e6c91f4ec31..7459b0e05e3a 100644 --- a/core/java/android/app/admin/SystemUpdateInfo.java +++ b/core/java/android/app/admin/SystemUpdateInfo.java @@ -133,7 +133,7 @@ public void writeToXml(TypedXmlSerializer out, String tag) throws IOException { out.startTag(null, tag); out.attributeLong(null, ATTR_RECEIVED_TIME, mReceivedTime); out.attributeInt(null, ATTR_SECURITY_PATCH_STATE, mSecurityPatchState); - out.attribute(null, ATTR_ORIGINAL_BUILD , Build.FINGERPRINT); + out.attribute(null, ATTR_ORIGINAL_BUILD , Build.VERSION.INCREMENTAL); out.endTag(null, tag); } @@ -142,7 +142,7 @@ public void writeToXml(TypedXmlSerializer out, String tag) throws IOException { public static SystemUpdateInfo readFromXml(TypedXmlPullParser parser) { // If an OTA has been applied (build fingerprint has changed), discard stale info. final String buildFingerprint = parser.getAttributeValue(null, ATTR_ORIGINAL_BUILD ); - if (!Build.FINGERPRINT.equals(buildFingerprint)) { + if (!Build.VERSION.INCREMENTAL.equals(buildFingerprint)) { return null; } try { diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 031380dc1962..bb5a3716af99 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4397,6 +4397,13 @@ public static Intent createChooser(Intent target, CharSequence title, IntentSend */ public static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED"; + /** + * Broadcast Action: Display power state has changed. + * @hide + */ + public static final String ACTION_DISPLAY_STATE_CHANGED = + "android.intent.action.DISPLAY_STATE_CHANGED"; + /** * Activity Action: Allow the user to select and return one or more existing * documents. When invoked, the system will display the various @@ -6990,6 +6997,15 @@ public static Intent createChooser(Intent target, CharSequence title, IntentSend public static final String EXTRA_IS_RESTORE = "android.intent.extra.IS_RESTORE"; + /** + * Broadcast action: notify the system that the user has performed a gesture on the screen + * to launch the camera. Broadcast should be protected to receivers holding the + * {@link Manifest.permission#STATUS_BAR_SERVICE} permission. + * @hide + */ + public static final String ACTION_SCREEN_CAMERA_GESTURE = + "android.intent.action.SCREEN_CAMERA_GESTURE"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Intent flags (see mFlags variable). diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 4b579e7db9f8..1cc9fd0cbd39 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -4675,43 +4675,7 @@ private Activity parseActivity(Package owner, Resources res, } private void setActivityResizeMode(ActivityInfo aInfo, TypedArray sa, Package owner) { - final boolean appExplicitDefault = (owner.applicationInfo.privateFlags - & (PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE - | PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE)) != 0; - - if (sa.hasValue(R.styleable.AndroidManifestActivity_resizeableActivity) - || appExplicitDefault) { - // Activity or app explicitly set if it is resizeable or not; - final boolean appResizeable = (owner.applicationInfo.privateFlags - & PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE) != 0; - if (sa.getBoolean(R.styleable.AndroidManifestActivity_resizeableActivity, - appResizeable)) { - aInfo.resizeMode = RESIZE_MODE_RESIZEABLE; - } else { - aInfo.resizeMode = RESIZE_MODE_UNRESIZEABLE; - } - return; - } - - if ((owner.applicationInfo.privateFlags - & PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) != 0) { - // The activity or app didn't explicitly set the resizing option, however we want to - // make it resize due to the sdk version it is targeting. - aInfo.resizeMode = RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; - return; - } - - // resize preference isn't set and target sdk version doesn't support resizing apps by - // default. For the app to be resizeable if it isn't fixed orientation or immersive. - if (aInfo.isFixedOrientationPortrait()) { - aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY; - } else if (aInfo.isFixedOrientationLandscape()) { - aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY; - } else if (aInfo.isFixedOrientation()) { - aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION; - } else { - aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZEABLE; - } + aInfo.resizeMode = RESIZE_MODE_RESIZEABLE; } /** diff --git a/core/java/android/content/pm/PackagePartitions.java b/core/java/android/content/pm/PackagePartitions.java index ff80e614be58..da3b68ecf789 100644 --- a/core/java/android/content/pm/PackagePartitions.java +++ b/core/java/android/content/pm/PackagePartitions.java @@ -131,7 +131,7 @@ private static String getFingerprint() { final String partitionName = SYSTEM_PARTITIONS.get(i).getName(); digestProperties[i] = "ro." + partitionName + ".build.fingerprint"; } - digestProperties[SYSTEM_PARTITIONS.size()] = "ro.build.fingerprint"; // build fingerprint + digestProperties[SYSTEM_PARTITIONS.size()] = "ro.build.version.incremental"; return SystemProperties.digestOf(digestProperties); } diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index 68b5d782bfbf..908999b64961 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -124,11 +124,13 @@ public final class ApkAssets { @Nullable @GuardedBy("this") - private final StringBlock mStringBlock; // null or closed if mNativePtr = 0. + private StringBlock mStringBlock; // null or closed if mNativePtr = 0. @PropertyFlags private final int mFlags; + private final boolean mIsOverlay; + @Nullable private final AssetsProvider mAssets; @@ -302,40 +304,43 @@ public static ApkAssets loadEmptyForLoader(@PropertyFlags int flags, private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { + this(format, flags, assets); Objects.requireNonNull(path, "path"); - mFlags = flags; mNativePtr = nativeLoad(format, path, flags, assets); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); - mAssets = assets; } private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { + this(format, flags, assets); Objects.requireNonNull(fd, "fd"); Objects.requireNonNull(friendlyName, "friendlyName"); - mFlags = flags; mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); - mAssets = assets; } private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { + this(format, flags, assets); Objects.requireNonNull(fd, "fd"); Objects.requireNonNull(friendlyName, "friendlyName"); - mFlags = flags; mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); - mAssets = assets; } private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) { - mFlags = flags; + this(FORMAT_APK, flags, assets); mNativePtr = nativeLoadEmpty(flags, assets); mStringBlock = null; + } + + private ApkAssets(@FormatType int format, @PropertyFlags int flags, + @Nullable AssetsProvider assets) { + mFlags = flags; mAssets = assets; + mIsOverlay = format == FORMAT_IDMAP; } @UnsupportedAppUsage @@ -425,6 +430,18 @@ public boolean isUpToDate() { } } + public boolean isSystem() { + return (mFlags & PROPERTY_SYSTEM) != 0; + } + + public boolean isSharedLib() { + return (mFlags & PROPERTY_DYNAMIC) != 0; + } + + public boolean isOverlay() { + return mIsOverlay; + } + @Override public String toString() { return "ApkAssets{path=" + getDebugName() + "}"; diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index e6b93427f413..bcaceb24d767 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -203,9 +203,25 @@ static void resetDrawableStateCache() { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics, @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) { - mAssets = assets; - mAppliedSharedLibsHash = - ResourcesManager.getInstance().updateResourceImplWithRegisteredLibs(this); + // Don't reuse assets by default as we have no control over whether they're already + // inside some other ResourcesImpl. + this(assets, metrics, config, displayAdjustments, false); + } + + public ResourcesImpl(@NonNull ResourcesImpl orig) { + // We know for sure that the other assets are in use, so can't reuse the object here. + this(orig.getAssets(), orig.getMetrics(), orig.getConfiguration(), + orig.getDisplayAdjustments(), false); + } + + public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics, + @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments, + boolean reuseAssets) { + final var assetsAndHash = + ResourcesManager.getInstance().updateResourceImplAssetsWithRegisteredLibs(assets, + reuseAssets); + mAssets = assetsAndHash.first; + mAppliedSharedLibsHash = assetsAndHash.second; mMetrics.setToDefaults(); mDisplayAdjustments = displayAdjustments; mConfiguration.setToDefaults(); diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java index c7fcc1a38244..f7147a27611a 100644 --- a/core/java/android/content/res/ThemedResourceCache.java +++ b/core/java/android/content/res/ThemedResourceCache.java @@ -27,6 +27,7 @@ import android.util.LongSparseArray; import java.lang.ref.WeakReference; +import java.util.HashMap; /** * Data structure used for caching data against themes. @@ -37,7 +38,7 @@ abstract class ThemedResourceCache { public static final int UNDEFINED_GENERATION = -1; @UnsupportedAppUsage - private ArrayMap>> mThemedEntries; + private HashMap>> mThemedEntries; private LongSparseArray> mUnthemedEntries; private LongSparseArray> mNullThemedEntries; @@ -176,7 +177,7 @@ private LongSparseArray> getThemedLocked(@Nullable Theme t, boo if (mThemedEntries == null) { if (create) { - mThemedEntries = new ArrayMap<>(1); + mThemedEntries = new HashMap<>(1); } else { return null; } @@ -220,11 +221,8 @@ private LongSparseArray> getUnthemedLocked(boolean create) { */ private boolean pruneLocked(@Config int configChanges) { if (mThemedEntries != null) { - for (int i = mThemedEntries.size() - 1; i >= 0; i--) { - if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) { - mThemedEntries.removeAt(i); - } - } + mThemedEntries.entrySet() + .removeIf(entry -> pruneEntriesLocked(entry.getValue(), configChanges)); } pruneEntriesLocked(mNullThemedEntries, configChanges); diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 8bff624451e7..eb8df301ce0f 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -846,6 +846,9 @@ private void beginTransaction(@Nullable SQLiteTransactionListener listener, int boolean readOnly = (mode == SQLiteSession.TRANSACTION_MODE_DEFERRED); getThreadSession().beginTransaction(mode, listener, getThreadDefaultConnectionFlags(readOnly), null); + } catch (SQLiteDatabaseCorruptException ex) { + onCorruption(); + throw ex; } finally { releaseReference(); } @@ -859,6 +862,9 @@ public void endTransaction() { acquireReference(); try { getThreadSession().endTransaction(null); + } catch (SQLiteDatabaseCorruptException ex) { + onCorruption(); + throw ex; } finally { releaseReference(); } @@ -976,6 +982,9 @@ private boolean yieldIfContendedHelper(boolean throwIfUnsafe, long sleepAfterYie acquireReference(); try { return getThreadSession().yieldTransaction(sleepAfterYieldDelay, throwIfUnsafe, null); + } catch (SQLiteDatabaseCorruptException ex) { + onCorruption(); + throw ex; } finally { releaseReference(); } @@ -2900,33 +2909,17 @@ public List> getAttachedDbs() { public boolean isDatabaseIntegrityOk() { acquireReference(); try { - List> attachedDbs = null; + SQLiteStatement prog = null; try { - attachedDbs = getAttachedDbs(); - if (attachedDbs == null) { - throw new IllegalStateException("databaselist for: " + getPath() + " couldn't " + - "be retrieved. probably because the database is closed"); - } - } catch (SQLiteException e) { - // can't get attachedDb list. do integrity check on the main database - attachedDbs = new ArrayList>(); - attachedDbs.add(new Pair("main", getPath())); - } - - for (int i = 0; i < attachedDbs.size(); i++) { - Pair p = attachedDbs.get(i); - SQLiteStatement prog = null; - try { - prog = compileStatement("PRAGMA " + p.first + ".integrity_check(1);"); - String rslt = prog.simpleQueryForString(); - if (!rslt.equalsIgnoreCase("ok")) { - // integrity_checker failed on main or attached databases - Log.e(TAG, "PRAGMA integrity_check on " + p.second + " returned: " + rslt); - return false; - } - } finally { - if (prog != null) prog.close(); + prog = compileStatement("PRAGMA integrity_check(1);"); + String rslt = prog.simpleQueryForString(); + if (!rslt.equalsIgnoreCase("ok")) { + // integrity_checker failed on main or attached databases + Log.e(TAG, "PRAGMA integrity_check returned: " + rslt); + return false; } + } finally { + if (prog != null) prog.close(); } } finally { releaseReference(); diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index ca3e3d2ad61b..355f5711db06 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -50,6 +50,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.text.TextUtils; import android.util.Log; import android.view.Surface; @@ -63,6 +64,7 @@ import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Objects; @@ -266,6 +268,25 @@ public class Camera { */ private static final int CAMERA_FACE_DETECTION_SW = 1; + /** + * @hide + */ + public static boolean shouldExposeAuxCamera() { + /** + * Force to expose only two cameras + * if the package name does not falls in this bucket + */ + String packageName = ActivityThread.currentOpPackageName(); + if (packageName == null) + return true; + List packageList = Arrays.asList( + SystemProperties.get("vendor.camera.aux.packagelist", packageName).split(",")); + List packageExcludelist = Arrays.asList( + SystemProperties.get("vendor.camera.aux.packageexcludelist", "").split(",")); + + return packageList.contains(packageName) && !packageExcludelist.contains(packageName); + } + /** * Returns the number of physical cameras available on this device. * The return value of this method might change dynamically if the device @@ -282,7 +303,12 @@ public class Camera { * cameras or an error was encountered enumerating them. */ public static int getNumberOfCameras() { - return getNumberOfCameras(ActivityThread.currentApplication().getApplicationContext()); + int numberOfCameras = + getNumberOfCameras(ActivityThread.currentApplication().getApplicationContext()); + if (!shouldExposeAuxCamera() && numberOfCameras > 2) { + numberOfCameras = 2; + } + return numberOfCameras; } /** @@ -313,6 +339,9 @@ public static int getNumberOfCameras(@NonNull Context context) { * low-level failure). */ public static void getCameraInfo(int cameraId, CameraInfo cameraInfo) { + if (cameraId >= getNumberOfCameras()) { + throw new RuntimeException("Unknown camera ID"); + } Context context = ActivityThread.currentApplication().getApplicationContext(); final int rotationOverride = CameraManager.getRotationOverride(context); getCameraInfo(cameraId, context, rotationOverride, cameraInfo); @@ -591,6 +620,9 @@ private boolean shouldForceSlowJpegMode() { /** used by Camera#open, Camera#open(int) */ Camera(int cameraId, @NonNull Context context, int rotationOverride) { + if (cameraId >= getNumberOfCameras()) { + throw new RuntimeException("Unknown camera ID"); + } Objects.requireNonNull(context); final int err = cameraInit(cameraId, context, rotationOverride); if (checkInitErrors(err)) { diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 21627920f598..3f1ed89b943a 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -40,6 +40,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Point; +import android.hardware.Camera; import android.hardware.CameraExtensionSessionStats; import android.hardware.CameraStatus; import android.hardware.ICameraService; @@ -2350,7 +2351,9 @@ public void injectSessionParams(@NonNull String cameraId, private String[] extractCameraIdListLocked(int deviceId, int devicePolicy) { List cameraIds = new ArrayList<>(); + boolean exposeAuxCamera = Camera.shouldExposeAuxCamera(); for (int i = 0; i < mDeviceStatus.size(); i++) { + if (!exposeAuxCamera && i == 2) break; int status = mDeviceStatus.valueAt(i); DeviceCameraInfo info = mDeviceStatus.keyAt(i); if (status == ICameraServiceListener.STATUS_NOT_PRESENT @@ -2920,6 +2923,11 @@ private void updateCallbackLocked(AvailabilityCallback callback, Executor execut } private void onStatusChangedLocked(int status, DeviceCameraInfo info) { + if (!Camera.shouldExposeAuxCamera() && Integer.parseInt(info.mCameraId) >= 2) { + Log.w(TAG, String.format("Ignoring status update of camera %d", info.mDeviceId)); + return; + } + if (DEBUG) { Log.v(TAG, String.format("Camera id %s has status changed to 0x%x for device %d", diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 3f5ae9196577..70ced33b4508 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -236,7 +236,7 @@ public CameraMetadataNative.Key getNativeKey() { private static final ArraySet mEmptySurfaceSet = new ArraySet(); private String mLogicalCameraId; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage private CameraMetadataNative mLogicalCameraSettings; private final HashMap mPhysicalCameraSettings = new HashMap(); diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index b9eba9c1d541..b3b24cb78d38 100644 --- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -222,7 +222,8 @@ private void checkCaptureRequest(CaptureRequest request) { } else if (request.isReprocess() && !isReprocessable()) { throw new IllegalArgumentException("this capture session cannot handle reprocess " + "requests"); - } else if (request.isReprocess() && request.getReprocessableSessionId() != mId) { + } else if (!mDeviceImpl.isPrivilegedApp() && + request.isReprocess() && request.getReprocessableSessionId() != mId) { throw new IllegalArgumentException("capture request was created for another session"); } } diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 84072585d7f0..caad0e5ea7b7 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -18,6 +18,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable; +import android.app.ActivityThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.compat.CompatChanges; @@ -58,6 +59,8 @@ import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.SystemClock; +import android.os.SystemProperties; +import android.text.TextUtils; import android.util.Log; import android.util.Range; import android.util.Size; @@ -185,6 +188,7 @@ public Thread newThread(Runnable r) { private int mNextSessionId = 0; private final int mAppTargetSdkVersion; + private boolean mIsPrivilegedApp = false; private ExecutorService mOfflineSwitchService; private CameraOfflineSessionImpl mOfflineSessionImpl; @@ -391,6 +395,7 @@ public CameraDeviceImpl(String cameraId, StateCallback callback, Executor execut } else { mTotalPartialCount = partialCount; } + mIsPrivilegedApp = checkPrivilegedAppList(); } private Map getPhysicalIdToChars() { @@ -1635,6 +1640,27 @@ private boolean checkInputConfigurationWithStreamConfigurations( return false; } + private boolean checkPrivilegedAppList() { + String packageName = ActivityThread.currentOpPackageName(); + String packageList = SystemProperties.get("persist.vendor.camera.privapp.list"); + + if (packageList.length() > 0) { + TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(','); + splitter.setString(packageList); + for (String str : splitter) { + if (packageName.equals(str)) { + return true; + } + } + } + + return false; + } + + public boolean isPrivilegedApp() { + return mIsPrivilegedApp; + } + private void checkInputConfiguration(InputConfiguration inputConfig) { if (inputConfig == null) { return; @@ -1677,6 +1703,15 @@ private void checkInputConfiguration(InputConfiguration inputConfig) { inputConfig.getWidth() + "x" + inputConfig.getHeight() + " is not valid"); } } else { + /* + * don't check input format and size, + * if the package name is in the white list + */ + if (isPrivilegedApp()) { + Log.w(TAG, "ignore input format/size check for white listed app"); + return; + } + if (!checkInputConfigurationWithStreamConfigurations(inputConfig, /*maxRes*/false) && !checkInputConfigurationWithStreamConfigurations(inputConfig, /*maxRes*/true)) { throw new IllegalArgumentException("Input config with format " + diff --git a/core/java/android/hardware/camera2/impl/CaptureResultExtras.java b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java index 8bf94986a490..0257ab979cb1 100644 --- a/core/java/android/hardware/camera2/impl/CaptureResultExtras.java +++ b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java @@ -75,6 +75,20 @@ public CaptureResultExtras(int requestId, int subsequenceId, int afTriggerId, this.readoutTimestamp = readoutTimestamp; } + // Backwards-compatible constructor + public CaptureResultExtras(int requestId, int subsequenceId, int afTriggerId, + int precaptureTriggerId, long frameNumber, + int partialResultCount, int errorStreamId, + String errorPhysicalCameraId, long lastCompletedRegularFrameNumber, + long lastCompletedReprocessFrameNumber, + long lastCompletedZslFrameNumber) { + this(requestId, subsequenceId, afTriggerId, precaptureTriggerId, frameNumber, + partialResultCount, errorStreamId, errorPhysicalCameraId, + lastCompletedRegularFrameNumber, lastCompletedReprocessFrameNumber, + lastCompletedZslFrameNumber, + false /*hasReadOutTimestamp*/, 0 /*readoutTimestamp*/); + } + @Override public int describeContents() { return 0; diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index e3dbb2bbbf90..857951cf40be 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -138,6 +138,66 @@ public StreamConfigurationMap( /*enforceImplementationDefined*/ true); } + /** + * Create a new {@link StreamConfigurationMap}. + * + *

The array parameters ownership is passed to this object after creation; do not + * write to them after this constructor is invoked.

+ * + * @param configurations a non-{@code null} array of {@link StreamConfiguration} + * @param minFrameDurations a non-{@code null} array of {@link StreamConfigurationDuration} + * @param stallDurations a non-{@code null} array of {@link StreamConfigurationDuration} + * @param depthConfigurations a non-{@code null} array of depth {@link StreamConfiguration} + * @param depthMinFrameDurations a non-{@code null} array of depth + * {@link StreamConfigurationDuration} + * @param depthStallDurations a non-{@code null} array of depth + * {@link StreamConfigurationDuration} + * @param dynamicDepthConfigurations a non-{@code null} array of dynamic depth + * {@link StreamConfiguration} + * @param dynamicDepthMinFrameDurations a non-{@code null} array of dynamic depth + * {@link StreamConfigurationDuration} + * @param dynamicDepthStallDurations a non-{@code null} array of dynamic depth + * {@link StreamConfigurationDuration} + * @param heicConfigurations a non-{@code null} array of heic {@link StreamConfiguration} + * @param heicMinFrameDurations a non-{@code null} array of heic + * {@link StreamConfigurationDuration} + * @param heicStallDurations a non-{@code null} array of heic + * {@link StreamConfigurationDuration} + * @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if + * camera device does not support high speed video recording + * @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE + * and thus needs a separate list of slow high-resolution output sizes + * @throws NullPointerException if any of the arguments except highSpeedVideoConfigurations + * were {@code null} or any subelements were {@code null} + * + * @hide + */ + public StreamConfigurationMap( + StreamConfiguration[] configurations, + StreamConfigurationDuration[] minFrameDurations, + StreamConfigurationDuration[] stallDurations, + StreamConfiguration[] depthConfigurations, + StreamConfigurationDuration[] depthMinFrameDurations, + StreamConfigurationDuration[] depthStallDurations, + StreamConfiguration[] dynamicDepthConfigurations, + StreamConfigurationDuration[] dynamicDepthMinFrameDurations, + StreamConfigurationDuration[] dynamicDepthStallDurations, + StreamConfiguration[] heicConfigurations, + StreamConfigurationDuration[] heicMinFrameDurations, + StreamConfigurationDuration[] heicStallDurations, + HighSpeedVideoConfiguration[] highSpeedVideoConfigurations, + ReprocessFormatsMap inputOutputFormatsMap, + boolean listHighResolution) { + this(configurations, minFrameDurations, stallDurations, + depthConfigurations, depthMinFrameDurations, depthStallDurations, + dynamicDepthConfigurations, dynamicDepthMinFrameDurations, + dynamicDepthStallDurations, + heicConfigurations, heicMinFrameDurations, heicStallDurations, + null /*jpegRConfigurations*/, null /*jpegRMinFrameDurations*/, null /*jpegRStallDurations*/, + highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution, + /*enforceImplementationDefined*/ true); + } + /** * Create a new {@link StreamConfigurationMap}. * diff --git a/core/java/android/hardware/camera2/utils/SurfaceUtils.java b/core/java/android/hardware/camera2/utils/SurfaceUtils.java index 6c83057fdf29..a624a3bbc811 100644 --- a/core/java/android/hardware/camera2/utils/SurfaceUtils.java +++ b/core/java/android/hardware/camera2/utils/SurfaceUtils.java @@ -20,11 +20,14 @@ import static com.android.internal.util.Preconditions.checkNotNull; +import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.ImageFormat; import android.graphics.PixelFormat; import android.hardware.HardwareBuffer; import android.hardware.camera2.params.StreamConfigurationMap; +import android.os.SystemProperties; +import android.text.TextUtils; import android.util.Range; import android.util.Size; import android.view.Surface; @@ -241,6 +244,11 @@ public static void checkConstrainedHighSpeedSurfaces(Collection surface + " the size must be 1 or 2"); } + if (isPrivilegedApp()) { + //skip checks for privileged apps + return; + } + List highSpeedSizes = null; if (fpsRange == null) { highSpeedSizes = Arrays.asList(config.getHighSpeedVideoSizes()); @@ -303,4 +311,21 @@ private static native int nativeDetectSurfaceDimens(Surface surface, /*out*/int[/*2*/] dimens); private static native long nativeGetSurfaceId(Surface surface); + + private static boolean isPrivilegedApp() { + String packageName = ActivityThread.currentOpPackageName(); + String packageList = SystemProperties.get("persist.vendor.camera.privapp.list"); + + if (packageList.length() > 0) { + TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(','); + splitter.setString(packageList); + for (String str : splitter) { + if (packageName.equals(str)) { + return true; + } + } + } + + return false; + } } diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java index 47541ca16cda..6d339624f8cc 100644 --- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java +++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java @@ -51,7 +51,8 @@ public class AmbientDisplayConfiguration { Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE, Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE, - Settings.Secure.DOZE_TAP_SCREEN_GESTURE + Settings.Secure.DOZE_TAP_SCREEN_GESTURE, + Settings.Secure.DOZE_FOR_NOTIFICATIONS }; /** Non-user configurable doze settings */ @@ -96,6 +97,12 @@ public boolean pulseOnNotificationAvailable() { && ambientDisplayAvailable(); } + /** @hide */ + public boolean userPulseOnNotificationEnabled(int user) { + return boolSettingDefaultOn(Settings.Secure.DOZE_FOR_NOTIFICATIONS, user) + && pulseOnNotificationEnabled(user); + } + /** @hide */ public boolean pickupGestureEnabled(int user) { return boolSetting(Settings.Secure.DOZE_PICK_UP_GESTURE, user, @@ -103,6 +110,12 @@ public boolean pickupGestureEnabled(int user) { && dozePickupSensorAvailable(); } + /** @hide */ + public boolean pickupGestureAmbient(int user) { + return boolSettingDefaultOff(Settings.Secure.DOZE_PICK_UP_GESTURE_AMBIENT, user) + && pickupGestureEnabled(user) && pulseOnNotificationEnabled(user); + } + /** @hide */ public boolean dozePickupSensorAvailable() { return mContext.getResources().getBoolean(R.bool.config_dozePulsePickup); @@ -114,6 +127,12 @@ public boolean tapGestureEnabled(int user) { && tapSensorAvailable(); } + /** @hide */ + public boolean tapGestureAmbient(int user) { + return boolSettingDefaultOff(Settings.Secure.DOZE_TAP_GESTURE_AMBIENT, user) + && tapGestureEnabled(user) && pulseOnNotificationEnabled(user); + } + /** @hide */ public boolean tapSensorAvailable() { for (String tapType : tapSensorTypeMapping()) { diff --git a/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java b/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java index d481153fc642..9febcab61065 100644 --- a/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java +++ b/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java @@ -17,7 +17,9 @@ package android.hardware.fingerprint; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; +import android.content.res.TypedArray; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.common.CommonProps; @@ -25,16 +27,22 @@ import android.hardware.biometrics.fingerprint.FingerprintSensorType; import android.hardware.biometrics.fingerprint.SensorLocation; import android.hardware.biometrics.fingerprint.SensorProps; +import android.util.Slog; import com.android.internal.R; import com.android.internal.util.ArrayUtils; +import java.util.ArrayList; +import java.util.List; + /** * Parse HIDL fingerprint sensor config and map it to SensorProps.aidl to match AIDL. * See core/res/res/values/config.xml config_biometric_sensors * @hide */ public final class HidlFingerprintSensorConfig extends SensorProps { + private static final String TAG = "HidlFingerprintSensorConfig"; + private int mSensorId; private int mModality; private int mStrength; @@ -70,6 +78,10 @@ private void mapHidlToAidlSensorConfiguration(@NonNull Context context) { halControlsIllumination = false; sensorLocations = new SensorLocation[1]; + // Non-empty workaroundLocations indicates that the sensor is SFPS. + final List workaroundLocations = + getWorkaroundSensorProps(context); + final int[] udfpsProps = context.getResources().getIntArray( com.android.internal.R.array.config_udfps_sensor_props); final boolean isUdfps = !ArrayUtils.isEmpty(udfpsProps); @@ -78,7 +90,7 @@ private void mapHidlToAidlSensorConfiguration(@NonNull Context context) { R.bool.config_is_powerbutton_fps); if (isUdfps) { - sensorType = FingerprintSensorType.UNKNOWN; + sensorType = FingerprintSensorType.UNDER_DISPLAY_OPTICAL; } else if (isPowerbuttonFps) { sensorType = FingerprintSensorType.POWER_BUTTON; } else { @@ -87,6 +99,9 @@ private void mapHidlToAidlSensorConfiguration(@NonNull Context context) { if (isUdfps && udfpsProps.length == 3) { setSensorLocation(udfpsProps[0], udfpsProps[1], udfpsProps[2]); + } else if (!workaroundLocations.isEmpty()) { + sensorLocations = new SensorLocation[workaroundLocations.size()]; + workaroundLocations.toArray(sensorLocations); } else { setSensorLocation(540 /* sensorLocationX */, 1636 /* sensorLocationY */, 130 /* sensorRadius */); @@ -103,6 +118,48 @@ private void setSensorLocation(int sensorLocationX, sensorLocations[0].sensorRadius = sensorRadius; } + // TODO(b/174868353): workaround for gaps in HAL interface (remove and get directly from HAL) + // reads values via an overlay instead of querying the HAL + @NonNull + public static List getWorkaroundSensorProps(@NonNull Context context) { + final List sensorLocations = new ArrayList<>(); + + final TypedArray sfpsProps = context.getResources().obtainTypedArray( + com.android.internal.R.array.config_sfps_sensor_props); + for (int i = 0; i < sfpsProps.length(); i++) { + final int id = sfpsProps.getResourceId(i, -1); + if (id > 0) { + final SensorLocation location = parseSensorLocation( + context.getResources().obtainTypedArray(id)); + if (location != null) { + sensorLocations.add(location); + } + } + } + sfpsProps.recycle(); + + return sensorLocations; + } + + @Nullable + private static SensorLocation parseSensorLocation(@Nullable TypedArray array) { + if (array == null) { + return null; + } + + try { + SensorLocation sensorLocation = new SensorLocation(); + sensorLocation.display = array.getString(0); + sensorLocation.sensorLocationX = array.getInt(1, 0); + sensorLocation.sensorLocationY = array.getInt(2, 0); + sensorLocation.sensorRadius = array.getInt(3, 0); + return sensorLocation; + } catch (Exception e) { + Slog.w(TAG, "malformed sensor location", e); + } + return null; + } + private byte authenticatorStrengthToPropertyStrength( @BiometricManager.Authenticators.Types int strength) { switch (strength) { diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index 8b267bf28c7e..f4f1d3d5ce96 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -166,6 +166,19 @@ public class BatteryManager { */ public static final String EXTRA_CHARGING_STATUS = "android.os.extra.CHARGING_STATUS"; + /** + * Int value representing the estimated battery full charge capacity in microampere-hours. + * {@hide} + */ + public static final String EXTRA_MAXIMUM_CAPACITY = "android.os.extra.MAXIMUM_CAPACITY"; + + /** + * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}: + * Int value representing the battery full charge design capacity in microampere-hours. + * {@hide} + */ + public static final String EXTRA_DESIGN_CAPACITY = "android.os.extra.DESIGN_CAPACITY"; + /** * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}: * Int value representing the battery's capacity level. These constants are key indicators of @@ -253,6 +266,12 @@ public class BatteryManager { @SystemApi public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP"; + /** + * boolean value to indicate OEM fast charging + * {@hide} + */ + public static final String EXTRA_OEM_FAST_CHARGING = "oem_fast_charging"; + // values for "status" field in the ACTION_BATTERY_CHANGED Intent public static final int BATTERY_STATUS_UNKNOWN = Constants.BATTERY_STATUS_UNKNOWN; public static final int BATTERY_STATUS_CHARGING = Constants.BATTERY_STATUS_CHARGING; diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index 14005b31903a..a7db5ded7e12 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -1413,11 +1413,13 @@ public static boolean createDir(File dir) { public static long roundStorageSize(long size) { long val = 1; long pow = 1; - while ((val * pow) < size) { + long pow1024 = 1; + while ((val * pow1024) < size) { val <<= 1; if (val > 512) { val = 1; pow *= 1000; + pow1024 *= 1024; } } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index e4c12b6ff834..57a2108cd1cf 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -853,6 +853,16 @@ public int hashCode() { */ public static final String REBOOT_RECOVERY = "recovery"; + /** + * The value to pass as the 'reason' argument to reboot() to reboot into + * bootloader mode if you need to get to the choppa (cit) + *

+ * Requires {@link android.Manifest.permission#REBOOT}). + *

+ * @hide + */ + public static final String REBOOT_BOOTLOADER = "bootloader"; + /** * The value to pass as the 'reason' argument to reboot() to reboot into * recovery mode for applying system updates. diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index 4a37e0a70443..39ef8e060a9a 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -181,6 +181,10 @@ private Trace() { @UnsupportedAppUsage @SystemApi(client = MODULE_LIBRARIES) public static boolean isTagEnabled(long traceTag) { + if (!Build.IS_DEBUGGABLE) { + return false; + } + return nativeIsTagEnabled(traceTag); } diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java index 0a8f62fd56d8..5be475ff0385 100644 --- a/core/java/android/os/UpdateEngine.java +++ b/core/java/android/os/UpdateEngine.java @@ -465,6 +465,17 @@ public void resetShouldSwitchSlotOnReboot() { } } + /** + * @hide + */ + public void setPerformanceMode(boolean enable) { + try { + mUpdateEngine.setPerformanceMode(enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Unbinds the last bound callback function. */ diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index e98397d104d6..d79ebe02b4ef 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -205,7 +205,7 @@ public final class PermissionManager { private static final int[] EXEMPTED_ROLES = {R.string.config_systemAmbientAudioIntelligence, R.string.config_systemUiIntelligence, R.string.config_systemAudioIntelligence, R.string.config_systemNotificationIntelligence, R.string.config_systemTextIntelligence, - R.string.config_systemVisualIntelligence}; + R.string.config_systemVisualIntelligence, R.string.config_systemTelephonyPackage}; private static final String[] INDICATOR_EXEMPTED_PACKAGES = new String[EXEMPTED_ROLES.length]; @@ -264,6 +264,9 @@ public final class PermissionManager { private List mSplitPermissionInfos; + private static String[] sLocationProviderPkgNames; + private static String[] sLocationExtraPkgNames; + /** * Creates a new instance. * @@ -1367,6 +1370,16 @@ public static Set getIndicatorExemptedPackages(@NonNull Context context) pkgNames.add(exemptedPackage); } } + for (String pkgName: sLocationProviderPkgNames) { + if (pkgName != null) { + pkgNames.add(pkgName); + } + } + for (String pkgName: sLocationExtraPkgNames) { + if (pkgName != null) { + pkgNames.add(pkgName); + } + } return pkgNames; } @@ -1382,6 +1395,10 @@ public static void updateIndicatorExemptedPackages(@NonNull Context context) { for (int i = 0; i < EXEMPTED_ROLES.length; i++) { INDICATOR_EXEMPTED_PACKAGES[i] = context.getString(EXEMPTED_ROLES[i]); } + sLocationProviderPkgNames = context.getResources().getStringArray( + R.array.config_locationProviderPackageNames); + sLocationExtraPkgNames = context.getResources().getStringArray( + R.array.config_locationExtraPackageNames); } } /** diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java index 0829d85801ac..c2dbfb8925ba 100644 --- a/core/java/android/provider/Downloads.java +++ b/core/java/android/provider/Downloads.java @@ -646,6 +646,11 @@ public static boolean isStatusCompleted(int status) { */ public static final int STATUS_QUEUED_FOR_WIFI = 196; + /** + * This download is paused manually. + */ + public static final int STATUS_PAUSED_MANUAL = 197; + /** * This download couldn't be completed due to insufficient storage * space. Typically, this is because the SD card is full. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ef8b3d738154..4de0abf226a6 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -122,6 +122,8 @@ import java.util.concurrent.Executor; import java.util.function.Consumer; +import com.android.internal.util.custom.DeviceConfigUtils; + /** * The Settings provider contains global system-level device preferences. */ @@ -5140,6 +5142,12 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean @Readable public static final String FOLD_LOCK_BEHAVIOR = "fold_lock_behavior_setting"; + /** + * Whether refresh rate should be switched to 60Hz on power save mode. + * @hide + */ + public static final String LOW_POWER_REFRESH_RATE = "low_power_rr_switch"; + /** * The amount of time in milliseconds before the device goes to sleep or begins * to dream after a period of inactivity. This value is also known as the @@ -5785,6 +5793,19 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean @Readable public static final String ACCELEROMETER_ROTATION = "accelerometer_rotation"; + /** + * Control the type of rotation which can be performed using the accelerometer + * if ACCELEROMETER_ROTATION is enabled. + * Value is a bitwise combination of + * 1 = 0 degrees (portrait) + * 2 = 90 degrees (left) + * 4 = 180 degrees (inverted portrait) + * 8 = 270 degrees (right) + * Setting to 0 is effectively orientation lock + * @hide + */ + public static final String ACCELEROMETER_ROTATION_ANGLES = "accelerometer_rotation_angles"; + /** * Default screen rotation when no other policy applies. * When {@link #ACCELEROMETER_ROTATION} is zero and no on-screen Activity expresses a @@ -6259,6 +6280,15 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean @Readable public static final String MULTI_AUDIO_FOCUS_ENABLED = "multi_audio_focus_enabled"; + /** + * Whether user can swap the order of the Alert Slider. + * * Whether user can invert the order of the Alert Slider. + * 0: Default + * 1: Inverted + * @hide + */ + public static final String ALERT_SLIDER_ORDER = "alert_slider_order"; + /** * The information of locale preference. This records user's preference to avoid * unsynchronized and existing locale preference in @@ -6272,6 +6302,39 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean */ public static final String LOCALE_PREFERENCES = "locale_preferences"; + /** + * Whether charging control should be enabled. + * The value is boolean (1 or 0). + * @hide + */ + public static final String CHARGING_CONTROL_ENABLED = "charging_control_enabled"; + + /** + * Charging control mode, one of AUTO (1; default), CUSTOM (2), or LIMIT (3). + * @hide + */ + public static final String CHARGING_CONTROL_MODE = "charging_control_mode"; + + /** + * Time when charging control is automatically activated in CUSTOM mode. + * The value is represented as seconds from midnight. + * @hide + */ + public static final String CHARGING_CONTROL_START_TIME = "charging_control_start_time"; + + /** + * Target time when battery is fully charged in CUSTOM mode. + * The value is represented as seconds from midnight. + * @hide + */ + public static final String CHARGING_CONTROL_TARGET_TIME = "charging_control_target_time"; + + /** + * Limit to stop charging. + * @hide + */ + public static final String CHARGING_CONTROL_LIMIT = "charging_control_charging_limit"; + /** * Setting to enable camera flash notification feature. *
    @@ -6302,6 +6365,136 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean public static final String SCREEN_FLASH_NOTIFICATION_COLOR = "screen_flash_notification_color_global"; + /** + * Color temperature of the display during the day + * @hide + */ + public static final String DISPLAY_TEMPERATURE_DAY = "display_temperature_day"; + + /** + * Color temperature of the display at night + * @hide + */ + public static final String DISPLAY_TEMPERATURE_NIGHT = "display_temperature_night"; + + /** + * Display color temperature adjustment mode, one of DAY (default), NIGHT, or AUTO. + * @hide + */ + public static final String DISPLAY_TEMPERATURE_MODE = "display_temperature_mode"; + + /** + * Automatic outdoor mode + * 0 = 0ff, 1 = on + * @hide + */ + public static final String DISPLAY_AUTO_OUTDOOR_MODE = "display_auto_outdoor_mode"; + + /** + * Reader mode + * 0 = 0ff, 1 = on + * @hide + */ + public static final String DISPLAY_READING_MODE = "display_reading_mode"; + + /** + * Use display power saving features such as CABC or CABL + * 0 = 0ff, 1 = on + * @hide + */ + public static final String DISPLAY_CABC = "display_low_power"; + + /** + * Use color enhancement feature of display + * 0 = 0ff, 1 = on + * @hide + */ + public static final String DISPLAY_COLOR_ENHANCE = "display_color_enhance"; + + /** + * Use auto contrast optimization feature of display + * 0 = 0ff, 1 = on + * @hide + */ + public static final String DISPLAY_AUTO_CONTRAST = "display_auto_contrast"; + + /** + * Manual display color adjustments (RGB values as floats, separated by spaces) + * @hide + */ + public static final String DISPLAY_COLOR_ADJUSTMENT = "display_color_adjustment"; + + /** + * The current custom picture adjustment values as a delimited string + * @hide + */ + public static final String DISPLAY_PICTURE_ADJUSTMENT = + "display_picture_adjustment"; + + /** + * Did we tell about how they can stop breaking their eyes? + * @hide + */ + public static final String LIVE_DISPLAY_HINTED = "live_display_hinted"; + + /** + * Enable statusbar double tap gesture on to put device to sleep + * 0 = 0ff, 1 = on + * @hide + */ + public static final String DOUBLE_TAP_SLEEP_GESTURE = "double_tap_sleep_gesture"; + + /** + * Anti flicker + * 0 = 0ff, 1 = on + * @hide + */ + public static final String DISPLAY_ANTI_FLICKER = "display_anti_flicker"; + + /** + * Whether or not to vibrate when a touchscreen gesture is detected + * @hide + */ + public static final String TOUCHSCREEN_GESTURE_HAPTIC_FEEDBACK = "touchscreen_gesture_haptic_feedback"; + + /** + * Whether the HighTouchPollingRate is activated or not. + * 0 = off, 1 = on + * @hide + */ + public static final String HIGH_TOUCH_POLLING_RATE_ENABLE = + "high_touch_polling_rate_enable"; + + /** + * Whether the HighTouchSensitivity is activated or not. + * 0 = off, 1 = on + * @hide + */ + public static final String HIGH_TOUCH_SENSITIVITY_ENABLE = + "high_touch_sensitivity_enable"; + + /** + * Adaptive playback + * Automatically pause media when the volume is muted and + * will resume automatically when volume is restored. + * 0 = disabled + * 1 = enabled + * @hide + */ + public static final String ADAPTIVE_PLAYBACK_ENABLED = "adaptive_playback_enabled"; + + /** + * Adaptive playback's timeout in ms + * @hide + */ + public static final String ADAPTIVE_PLAYBACK_TIMEOUT = "adaptive_playback_timeout"; + + /** + * Whether to take partial screenshot with volume down + power click. + * @hide + */ + public static final String CLICK_PARTIAL_SCREENSHOT = "click_partial_screenshot"; + /** * IMPORTANT: If you add a new public settings you also have to add it to * PUBLIC_SETTINGS below. If the new setting is hidden you have to add @@ -6309,6 +6502,84 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean * the setting value. See an example above. */ + /** + * @hide + */ + public static final String SCREENSHOT_SHUTTER_SOUND = "screenshot_shutter_sound"; + + /** + * Whether the torch launch gesture when the screen is off should be enabled. + * @hide + */ + public static final String TORCH_POWER_BUTTON_GESTURE = "torch_power_button_gesture"; + + /** + * Whether to show power menu on LockScreen + * @hide + */ + public static final String LOCKSCREEN_ENABLE_POWER_MENU = "lockscreen_enable_power_menu"; + + /** + * Whether to scramble a pin unlock layout + * 0 = 0ff, 1 = on + * @hide + */ + public static final String LOCKSCREEN_PIN_SCRAMBLE_LAYOUT = "lockscreen_scramble_pin_layout"; + + /** + * Wheter to show network traffic indicator in statusbar + * @hide + */ + public static final String NETWORK_TRAFFIC_STATE = "network_traffic_state"; + + /** + * Network traffic inactivity threshold + * @hide + */ + public static final String NETWORK_TRAFFIC_AUTOHIDE_THRESHOLD = "network_traffic_autohide_threshold"; + + /** + * Whether the phone vibrates on call connect + * @hide + */ + public static final String VIBRATE_ON_CONNECT = "vibrate_on_connect"; + + /** + * Whether the phone vibrates on call waiting + * @hide + */ + public static final String VIBRATE_ON_CALLWAITING = "vibrate_on_callwaiting"; + + /** + * Whether the phone vibrates on disconnect + * @hide + */ + public static final String VIBRATE_ON_DISCONNECT = "vibrate_on_disconnect"; + + /** + * Whether auto brightness is applied one shot when screen is turned on + * @hide + */ + public static final String AUTO_BRIGHTNESS_ONE_SHOT = "auto_brightness_one_shot"; + + /** + * Force full screen for devices with cutout + * @hide + */ + public static final String FORCE_FULLSCREEN_CUTOUT_APPS = "force_full_screen_cutout_apps"; + + /** + * Whether to show advanced reboot options in power menu + * @hide + */ + public static final String ADVANCED_REBOOT = "advanced_reboot"; + + /** + * Show app volume rows in volume panel + * @hide + */ + public static final String SHOW_APP_VOLUME = "show_app_volume"; + /** * Keys we no longer back up under the current schema, but want to continue to * process when restoring historical backup datasets. @@ -6321,6 +6592,30 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean public static final String[] LEGACY_RESTORE_SETTINGS = { }; + /** + * Three Finger Gesture from Oppo + * @hide + */ + public static final String THREE_FINGER_GESTURE = "three_finger_gesture"; + + /** + * Whether to enable smart 5G mode + * @hide + */ + public static final String SMART_5G = "smart_5g"; + + /** + * Whether or not volume button music controls should be enabled to seek media tracks + * @hide + */ + public static final String VOLBTN_MUSIC_CONTROLS = "volbtn_music_controls"; + + /** + * Current status of whether gestures are locked + * @hide + */ + public static final String LOCK_GESTURE_STATUS = "lock_gesture_status"; + /** * These are all public system settings * @@ -6444,6 +6739,25 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION); PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR); PRIVATE_SETTINGS.add(DEFAULT_DEVICE_FONT_SCALE); + PRIVATE_SETTINGS.add(DISPLAY_TEMPERATURE_DAY); + PRIVATE_SETTINGS.add(DISPLAY_TEMPERATURE_NIGHT); + PRIVATE_SETTINGS.add(DISPLAY_TEMPERATURE_MODE); + PRIVATE_SETTINGS.add(DISPLAY_AUTO_OUTDOOR_MODE); + PRIVATE_SETTINGS.add(DISPLAY_READING_MODE); + PRIVATE_SETTINGS.add(DISPLAY_CABC); + PRIVATE_SETTINGS.add(DISPLAY_COLOR_ENHANCE); + PRIVATE_SETTINGS.add(DISPLAY_AUTO_CONTRAST); + PRIVATE_SETTINGS.add(DISPLAY_COLOR_ADJUSTMENT); + PRIVATE_SETTINGS.add(DISPLAY_PICTURE_ADJUSTMENT); + PRIVATE_SETTINGS.add(LIVE_DISPLAY_HINTED); + PRIVATE_SETTINGS.add(DISPLAY_ANTI_FLICKER); + PRIVATE_SETTINGS.add(HIGH_TOUCH_POLLING_RATE_ENABLE); + PRIVATE_SETTINGS.add(HIGH_TOUCH_SENSITIVITY_ENABLE); + PRIVATE_SETTINGS.add(VIBRATE_ON_CONNECT); + PRIVATE_SETTINGS.add(VIBRATE_ON_CALLWAITING); + PRIVATE_SETTINGS.add(VIBRATE_ON_DISCONNECT); + PRIVATE_SETTINGS.add(AUTO_BRIGHTNESS_ONE_SHOT); + PRIVATE_SETTINGS.add(FORCE_FULLSCREEN_CUTOUT_APPS); } /** @@ -6830,10 +7144,13 @@ public static final class Secure extends NameValueTable { @UnsupportedAppUsage private static final HashSet MOVED_TO_GLOBAL; static { - MOVED_TO_LOCK_SETTINGS = new HashSet<>(3); + MOVED_TO_LOCK_SETTINGS = new HashSet<>(6); MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_ENABLED); MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_VISIBLE); MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED); + MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_SIZE); + MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_DOTS_VISIBLE); + MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_SHOW_ERROR_PATH); MOVED_TO_GLOBAL = new HashSet<>(); MOVED_TO_GLOBAL.add(Settings.Global.ADB_ENABLED); @@ -8357,6 +8674,24 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val public static final String LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED = "lock_pattern_tactile_feedback_enabled"; + /** + * Determines the width and height of the LockPatternView widget + * @hide + */ + public static final String LOCK_PATTERN_SIZE = "lock_pattern_size"; + + /** + * Whether lock pattern will show dots (0 = false, 1 = true) + * @hide + */ + public static final String LOCK_DOTS_VISIBLE = "lock_pattern_dotsvisible"; + + /** + * Whether lockscreen error pattern is visible (0 = false, 1 = true) + * @hide + */ + public static final String LOCK_SHOW_ERROR_PATH = "lock_pattern_show_error_path"; + /** * This preference allows the device to be locked given time after screen goes off, * subject to current DeviceAdmin policy limits. @@ -11774,7 +12109,7 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val /** * What behavior should be invoked when the volume hush gesture is triggered - * One of VOLUME_HUSH_OFF, VOLUME_HUSH_VIBRATE, VOLUME_HUSH_MUTE. + * One of VOLUME_HUSH_OFF, VOLUME_HUSH_VIBRATE, VOLUME_HUSH_MUTE, VOLUME_HUSH_CYCLE. * * @hide */ @@ -11791,6 +12126,8 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val /** @hide */ @SystemApi public static final int VOLUME_HUSH_MUTE = 2; + /** @hide */ + public static final int VOLUME_HUSH_CYCLE = 3; /** * The number of times (integer) the user has manually enabled battery saver. @@ -12116,6 +12453,12 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val @Readable public static final String TAP_GESTURE = "tap_gesture"; + /** + * Whether tethering is allowed to use VPN upstreams + */ + @SuppressLint({"NoSettingsProvider", "UnflaggedApi"}) + public static final String TETHERING_ALLOW_VPN_UPSTREAMS = "tethering_allow_vpn_upstreams"; + /** * Controls whether the people strip is enabled. * @hide @@ -12409,6 +12752,12 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val public static final String HBM_SETTING_KEY = "com.android.server.display.HBM_SETTING_KEY"; + /** + * Whether to use black theme for dark mode + * @hide + */ + public static final String BERRY_BLACK_THEME = "berry_black_theme"; + /** * Keys we no longer back up under the current schema, but want to continue to * process when restoring historical backup datasets. @@ -12660,6 +13009,12 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val */ public static final String HIDE_PRIVATESPACE_ENTRY_POINT = "hide_privatespace_entry_point"; + /** + * Whether touch hovering is enabled on supported hardware + * @hide + */ + public static final String FEATURE_TOUCH_HOVERING = "feature_touch_hovering"; + /** * Whether or not secure windows should be disabled. This only works on debuggable builds. * @@ -12711,6 +13066,32 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val @Readable public static final String STYLUS_POINTER_ICON_ENABLED = "stylus_pointer_icon_enabled"; + /** + * Whether to trigger doze for new notifications + * @hide + */ + public static final String DOZE_FOR_NOTIFICATIONS = "doze_for_notifications"; + + /** + * Whether to show ambient instead of waking for the tap gesture + * @hide + */ + public static final String DOZE_TAP_GESTURE_AMBIENT = "doze_tap_gesture_ambient"; + + /** + * Whether to show ambient instead of waking for the pickup gesture + * Do note quick pickup (device sensor) is already configured to do that + * @hide + */ + public static final String DOZE_PICK_UP_GESTURE_AMBIENT = "doze_pick_up_gesture_ambient"; + + /** + * Whether user is allowed to interact with quick settings on lockscreen. + * @hide + */ + public static final String QS_TILES_TOGGLEABLE_ON_LOCK_SCREEN = + "qs_tiles_toggleable_on_lock_screen"; + /** * These entries are considered common between the personal and the managed profile, * since the managed profile doesn't get to change them. @@ -12840,6 +13221,12 @@ public static void setLocationProviderEnabled(ContentResolver cr, */ @Readable public static final String CONTEXTUAL_SEARCH_PACKAGE = "contextual_search_package"; + + /** + * boolean value. toggles swipe up hint in gestural nav mode + * @hide + */ + public static final String NAVIGATION_BAR_HINT = "navigation_bar_hint"; } /** @@ -19251,6 +19638,13 @@ public static boolean putFloat(ContentResolver cr, String name, float value) { */ public static final String RESTRICTED_NETWORKING_MODE = "restricted_networking_mode"; + /** + * Control whether FLAG_SECURE is ignored for all windows. + * @hide + */ + @Readable + public static final String WINDOW_IGNORE_SECURE = "window_ignore_secure"; + /** * Setting indicating whether Low Power Standby is enabled, if supported. * @@ -20564,6 +20958,9 @@ public static Map getStrings(@NonNull ContentResolver resolver, @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean putString(@NonNull String namespace, @NonNull String name, @Nullable String value, boolean makeDefault) { + if (DeviceConfigUtils.shouldDenyDeviceConfigControl(namespace, name)) { + return true; + } ContentResolver resolver = getContentResolver(); return sNameValueCache.putStringForUser(resolver, createCompositeName(namespace, name), value, null, makeDefault, resolver.getUserId(), @@ -20585,7 +20982,9 @@ public static boolean putString(@NonNull String namespace, public static boolean setStrings(@NonNull String namespace, @NonNull Map keyValues) throws DeviceConfig.BadConfigException { - return setStrings(getContentResolver(), namespace, keyValues); + boolean result = setStrings(getContentResolver(), namespace, keyValues); + DeviceConfigUtils.setDefaultProperties(namespace, null); + return result; } /** @@ -20635,6 +21034,9 @@ public static boolean setStrings(@NonNull ContentResolver resolver, @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean deleteString(@NonNull String namespace, @NonNull String name) { + if (DeviceConfigUtils.shouldDenyDeviceConfigControl(namespace, name)) { + return true; + } ContentResolver resolver = getContentResolver(); return sNameValueCache.deleteStringForUser(resolver, createCompositeName(namespace, name), resolver.getUserId()); @@ -20671,6 +21073,7 @@ public static void resetToDefaults(@ResetMode int resetMode, } catch (RemoteException e) { Log.w(TAG, "Can't reset to defaults for " + CONTENT_URI, e); } + DeviceConfigUtils.setDefaultProperties(null, null); } /** @@ -21145,6 +21548,12 @@ private Panel() { @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_VOLUME = "android.settings.panel.action.VOLUME"; + + /** + * @hide + */ + public static final String ACTION_APP_VOLUME = + "android.settings.panel.action.APP_VOLUME"; } /** diff --git a/core/java/android/security/net/config/PinSet.java b/core/java/android/security/net/config/PinSet.java index d3c975eb3101..ff66b6c3be49 100644 --- a/core/java/android/security/net/config/PinSet.java +++ b/core/java/android/security/net/config/PinSet.java @@ -17,8 +17,10 @@ package android.security.net.config; import android.util.ArraySet; + import java.util.Collections; import java.util.Set; +import java.util.stream.Collectors; /** @hide */ public final class PinSet { @@ -26,6 +28,7 @@ public final class PinSet { new PinSet(Collections.emptySet(), Long.MAX_VALUE); public final long expirationTime; public final Set pins; + private final Set algorithms; public PinSet(Set pins, long expirationTime) { if (pins == null) { @@ -33,14 +36,12 @@ public PinSet(Set pins, long expirationTime) { } this.pins = pins; this.expirationTime = expirationTime; + this.algorithms = pins.stream() + .map(pin -> pin.digestAlgorithm) + .collect(Collectors.toCollection(ArraySet::new)); } Set getPinAlgorithms() { - // TODO: Cache this. - Set algorithms = new ArraySet(); - for (Pin pin : pins) { - algorithms.add(pin.digestAlgorithm); - } return algorithms; } } diff --git a/core/java/android/speech/tts/TtsEngines.java b/core/java/android/speech/tts/TtsEngines.java index a8aea7c1eb59..ef63c8ff751d 100644 --- a/core/java/android/speech/tts/TtsEngines.java +++ b/core/java/android/speech/tts/TtsEngines.java @@ -507,7 +507,7 @@ private static String parseEnginePrefFromList(String prefValue, String engineNam for (String value : prefValues) { final int delimiter = value.indexOf(':'); if (delimiter > 0) { - if (engineName.equals(value.substring(0, delimiter))) { + if ((value.substring(0, delimiter)).equals(engineName)) { return value.substring(delimiter + 1); } } @@ -560,7 +560,7 @@ private String updateValueInCommaSeparatedList(String list, String key, for (String value : prefValues) { final int delimiter = value.indexOf(':'); if (delimiter > 0) { - if (key.equals(value.substring(0, delimiter))) { + if (value.substring(0, delimiter).equals(key)) { if (first) { first = false; } else { diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 032f5923d3f2..a569abd01621 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -825,49 +825,46 @@ public void getChars(int start, int end, char[] dest, int destoff) { */ public static void writeToParcel(@Nullable CharSequence cs, @NonNull Parcel p, int parcelableFlags) { + if (cs == null) { + p.writeInt(1); + p.writeString8(null); + return; + } if (cs instanceof Spanned) { p.writeInt(0); - p.writeString8(cs.toString()); - - Spanned sp = (Spanned) cs; - Object[] os = sp.getSpans(0, cs.length(), Object.class); - - // note to people adding to this: check more specific types - // before more generic types. also notice that it uses - // "if" instead of "else if" where there are interfaces - // so one object can be several. - - for (int i = 0; i < os.length; i++) { - Object o = os[i]; - Object prop = os[i]; - - if (prop instanceof CharacterStyle) { - prop = ((CharacterStyle) prop).getUnderlying(); - } - - if (prop instanceof ParcelableSpan) { - final ParcelableSpan ps = (ParcelableSpan) prop; - final int spanTypeId = ps.getSpanTypeIdInternal(); - if (spanTypeId < FIRST_SPAN || spanTypeId > LAST_SPAN) { - Log.e(TAG, "External class \"" + ps.getClass().getSimpleName() - + "\" is attempting to use the frameworks-only ParcelableSpan" - + " interface"); - } else { - p.writeInt(spanTypeId); - ps.writeToParcelInternal(p, parcelableFlags); - writeWhere(p, sp, o); + StringBuilder csStringBuilder = new StringBuilder(cs.length()); + csStringBuilder.append(cs); + p.writeString8(csStringBuilder.toString()); + final Spanned sp = (Spanned) cs; + final Object[] os = sp.getSpans(0, cs.length(), Object.class); + if (os.length > 0) { + for (final Object o : os) { + Object prop = o; + if (prop instanceof CharacterStyle) { + prop = ((CharacterStyle) prop).getUnderlying(); + } + if (prop instanceof ParcelableSpan) { + final ParcelableSpan ps = (ParcelableSpan) prop; + final int spanTypeId = ps.getSpanTypeIdInternal(); + if (spanTypeId < FIRST_SPAN || spanTypeId > LAST_SPAN) { + StringBuilder errorMessage = new StringBuilder("External class \"") + .append(ps.getClass().getSimpleName()) + .append("\" is attempting to use the frameworks-only ParcelableSpan interface"); + Log.e(TAG, errorMessage.toString()); + } else { + p.writeInt(spanTypeId); + ps.writeToParcelInternal(p, parcelableFlags); + writeWhere(p, sp, o); + } } } } - p.writeInt(0); } else { p.writeInt(1); - if (cs != null) { - p.writeString8(cs.toString()); - } else { - p.writeString8(null); - } + StringBuilder csStringBuilder = new StringBuilder(cs.length()); + csStringBuilder.append(cs); + p.writeString8(csStringBuilder.toString()); } } diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index aad3bf2679d9..352f8f78d40b 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -688,67 +688,75 @@ public final View createView(@NonNull Context viewContext, @NonNull String name, throws ClassNotFoundException, InflateException { Objects.requireNonNull(viewContext); Objects.requireNonNull(name); - Constructor constructor = sConstructorMap.get(name); - if (constructor != null && !verifyClassLoader(constructor)) { - constructor = null; - sConstructorMap.remove(name); - } + String prefixedName = prefix != null ? (prefix + name) : name; Class clazz = null; try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, name); - if (constructor == null) { - // Class not found in the cache, see if it's real, and try to add it - clazz = Class.forName(prefix != null ? (prefix + name) : name, false, - mContext.getClassLoader()).asSubclass(View.class); - - if (mFilter != null && clazz != null) { - boolean allowed = mFilter.onLoadClass(clazz); - if (!allowed) { - failNotAllowed(name, prefix, viewContext, attrs); - } + // Opportunistically create view directly instead of using reflection + View view = tryCreateViewDirect(prefixedName, viewContext, attrs); + if (view == null) { + Constructor constructor = sConstructorMap.get(name); + if (constructor != null && !verifyClassLoader(constructor)) { + constructor = null; + sConstructorMap.remove(name); } - constructor = clazz.getConstructor(mConstructorSignature); - constructor.setAccessible(true); - sConstructorMap.put(name, constructor); - } else { - // If we have a filter, apply it to cached constructor - if (mFilter != null) { - // Have we seen this name before? - Boolean allowedState = mFilterMap.get(name); - if (allowedState == null) { - // New class -- remember whether it is allowed - clazz = Class.forName(prefix != null ? (prefix + name) : name, false, - mContext.getClassLoader()).asSubclass(View.class); - - boolean allowed = clazz != null && mFilter.onLoadClass(clazz); - mFilterMap.put(name, allowed); + + if (constructor == null) { + // Class not found in the cache, see if it's real, and try to add it + clazz = Class.forName(prefixedName, false, + mContext.getClassLoader()).asSubclass(View.class); + + if (mFilter != null && clazz != null) { + boolean allowed = mFilter.onLoadClass(clazz); if (!allowed) { failNotAllowed(name, prefix, viewContext, attrs); } - } else if (allowedState.equals(Boolean.FALSE)) { - failNotAllowed(name, prefix, viewContext, attrs); + } + constructor = clazz.getConstructor(mConstructorSignature); + constructor.setAccessible(true); + sConstructorMap.put(name, constructor); + } else { + // If we have a filter, apply it to cached constructor + if (mFilter != null) { + // Have we seen this name before? + Boolean allowedState = mFilterMap.get(name); + if (allowedState == null) { + // New class -- remember whether it is allowed + clazz = Class.forName(prefixedName, false, + mContext.getClassLoader()).asSubclass(View.class); + + boolean allowed = clazz != null && mFilter.onLoadClass(clazz); + mFilterMap.put(name, allowed); + if (!allowed) { + failNotAllowed(name, prefix, viewContext, attrs); + } + } else if (allowedState.equals(Boolean.FALSE)) { + failNotAllowed(name, prefix, viewContext, attrs); + } } } - } - Object lastContext = mConstructorArgs[0]; - mConstructorArgs[0] = viewContext; - Object[] args = mConstructorArgs; - args[1] = attrs; + Object lastContext = mConstructorArgs[0]; + mConstructorArgs[0] = viewContext; + Object[] args = mConstructorArgs; + args[1] = attrs; - try { - final View view = constructor.newInstance(args); - if (view instanceof ViewStub) { - // Use the same context when inflating ViewStub later. - final ViewStub viewStub = (ViewStub) view; - viewStub.setLayoutInflater(cloneInContext((Context) args[0])); + try { + view = constructor.newInstance(args); + } finally { + mConstructorArgs[0] = lastContext; } - return view; - } finally { - mConstructorArgs[0] = lastContext; } + + if (view instanceof ViewStub) { + // Use the same context when inflating ViewStub later. + final ViewStub viewStub = (ViewStub) view; + viewStub.setLayoutInflater(cloneInContext((Context) viewContext)); + } + + return view; } catch (NoSuchMethodException e) { final InflateException ie = new InflateException( getParserStateDescription(viewContext, attrs) @@ -1242,4 +1250,121 @@ protected void dispatchDraw(Canvas canvas) { } } } + + // Some of the views included here are deprecated, but apps still use them. + @SuppressWarnings("deprecation") + private static View tryCreateViewDirect(String name, Context context, AttributeSet attributeSet) { + // This contains all the framework views used in a set of 113 real-world apps, sorted by + // number of occurrences. While views with only 1 occurrence are unlikely to be worth + // optimizing, it doesn't hurt to include them because switch-case is compiled into a table + // lookup after calling String#hashCode(). + switch (name) { + case "android.widget.LinearLayout": // 13486 occurrences + return new android.widget.LinearLayout(context, attributeSet); + case "android.widget.View": // 6930 occurrences + case "android.webkit.View": // 63 occurrences + case "android.view.View": // 63 occurrences + case "android.app.View": // 62 occurrences + return new android.view.View(context, attributeSet); + case "android.widget.FrameLayout": // 6447 occurrences + return new android.widget.FrameLayout(context, attributeSet); + case "android.widget.ViewStub": // 5613 occurrences + case "android.view.ViewStub": // 228 occurrences + case "android.app.ViewStub": // 227 occurrences + case "android.webkit.ViewStub": // 226 occurrences + return new android.view.ViewStub(context, attributeSet); + case "android.widget.TextView": // 4722 occurrences + return new android.widget.TextView(context, attributeSet); + case "android.widget.ImageView": // 3044 occurrences + return new android.widget.ImageView(context, attributeSet); + case "android.widget.RelativeLayout": // 2665 occurrences + return new android.widget.RelativeLayout(context, attributeSet); + case "android.widget.Space": // 1694 occurrences + return new android.widget.Space(context, attributeSet); + case "android.widget.ProgressBar": // 770 occurrences + return new android.widget.ProgressBar(context, attributeSet); + case "android.widget.Button": // 382 occurrences + return new android.widget.Button(context, attributeSet); + case "android.widget.ImageButton": // 265 occurrences + return new android.widget.ImageButton(context, attributeSet); + case "android.widget.Switch": // 145 occurrences + return new android.widget.Switch(context, attributeSet); + case "android.widget.DateTimeView": // 117 occurrences + return new android.widget.DateTimeView(context, attributeSet); + case "android.widget.Toolbar": // 86 occurrences + return new android.widget.Toolbar(context, attributeSet); + case "android.widget.HorizontalScrollView": // 68 occurrences + return new android.widget.HorizontalScrollView(context, attributeSet); + case "android.widget.ScrollView": // 67 occurrences + return new android.widget.ScrollView(context, attributeSet); + case "android.widget.NotificationHeaderView": // 65 occurrences + case "android.webkit.NotificationHeaderView": // 65 occurrences + case "android.view.NotificationHeaderView": // 65 occurrences + case "android.app.NotificationHeaderView": // 65 occurrences + return new android.view.NotificationHeaderView(context, attributeSet); + case "android.widget.ListView": // 58 occurrences + return new android.widget.ListView(context, attributeSet); + case "android.widget.QuickContactBadge": // 50 occurrences + return new android.widget.QuickContactBadge(context, attributeSet); + case "android.widget.SeekBar": // 40 occurrences + return new android.widget.SeekBar(context, attributeSet); + case "android.widget.CheckBox": // 38 occurrences + return new android.widget.CheckBox(context, attributeSet); + case "android.widget.GridLayout": // 16 occurrences + return new android.widget.GridLayout(context, attributeSet); + case "android.widget.TableRow": // 15 occurrences + return new android.widget.TableRow(context, attributeSet); + case "android.widget.RadioGroup": // 15 occurrences + return new android.widget.RadioGroup(context, attributeSet); + case "android.widget.Chronometer": // 15 occurrences + return new android.widget.Chronometer(context, attributeSet); + case "android.widget.ViewFlipper": // 13 occurrences + return new android.widget.ViewFlipper(context, attributeSet); + case "android.widget.Spinner": // 9 occurrences + return new android.widget.Spinner(context, attributeSet); + case "android.widget.ViewSwitcher": // 8 occurrences + return new android.widget.ViewSwitcher(context, attributeSet); + case "android.widget.TextSwitcher": // 8 occurrences + return new android.widget.TextSwitcher(context, attributeSet); + case "android.widget.SurfaceView": // 8 occurrences + case "android.webkit.SurfaceView": // 1 occurrence + case "android.view.SurfaceView": // 1 occurrence + case "android.app.SurfaceView": // 1 occurrence + return new android.view.SurfaceView(context, attributeSet); + case "android.widget.CheckedTextView": // 8 occurrences + return new android.widget.CheckedTextView(context, attributeSet); + case "android.preference.PreferenceFrameLayout": // 8 occurrences + return new android.preference.PreferenceFrameLayout(context, attributeSet); + case "android.widget.TwoLineListItem": // 7 occurrences + return new android.widget.TwoLineListItem(context, attributeSet); + case "android.widget.TableLayout": // 5 occurrences + return new android.widget.TableLayout(context, attributeSet); + case "android.widget.EditText": // 5 occurrences + return new android.widget.EditText(context, attributeSet); + case "android.widget.TabWidget": // 3 occurrences + return new android.widget.TabWidget(context, attributeSet); + case "android.widget.TabHost": // 3 occurrences + return new android.widget.TabHost(context, attributeSet); + case "android.widget.ZoomButton": // 2 occurrences + return new android.widget.ZoomButton(context, attributeSet); + case "android.widget.TextureView": // 2 occurrences + case "android.webkit.TextureView": // 2 occurrences + case "android.app.TextureView": // 2 occurrences + case "android.view.TextureView": // 2 occurrences + return new android.view.TextureView(context, attributeSet); + case "android.widget.ExpandableListView": // 2 occurrences + return new android.widget.ExpandableListView(context, attributeSet); + case "android.widget.ViewAnimator": // 1 occurrence + return new android.widget.ViewAnimator(context, attributeSet); + case "android.widget.TextClock": // 1 occurrence + return new android.widget.TextClock(context, attributeSet); + case "android.widget.AutoCompleteTextView": // 1 occurrence + return new android.widget.AutoCompleteTextView(context, attributeSet); + case "android.widget.WebView": // 1 occurrence + case "android.webkit.WebView": // 1 occurrence + return new android.webkit.WebView(context, attributeSet); + } + + return null; + } } diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 63bf392b5ef1..e454b87a8d4b 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -272,7 +272,7 @@ public class ViewConfiguration { * The coefficient of friction applied to flings/scrolls. */ @UnsupportedAppUsage - private static final float SCROLL_FRICTION = 0.015f; + private static final float SCROLL_FRICTION = 0.012f; /** * Max distance in dips to overscroll for edge effects diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1cad81b3e030..0cefa5241815 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -102,8 +102,14 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS; @@ -301,6 +307,7 @@ import java.util.Objects; import java.util.OptionalInt; import java.util.Queue; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; @@ -340,6 +347,19 @@ public final class ViewRootImpl implements ViewParent, private static final int LOGTAG_INPUT_FOCUS = 62001; private static final int LOGTAG_VIEWROOT_DRAW_EVENT = 60004; + private static final Set NO_VOTE_WINDOW_TYPES = Set.of( + TYPE_INPUT_METHOD, + TYPE_INPUT_METHOD_DIALOG, + TYPE_KEYGUARD_DIALOG, + TYPE_NAVIGATION_BAR, + TYPE_NAVIGATION_BAR_PANEL, + TYPE_STATUS_BAR, + TYPE_SYSTEM_ALERT, + TYPE_SYSTEM_DIALOG, + TYPE_TOAST, + TYPE_VOLUME_OVERLAY + ); + /** * This change disables the {@code DRAW_WAKE_LOCK}, an internal wakelock acquired per-frame * duration display DOZE. It was added to allow animation during AOD. This wakelock consumes @@ -3419,6 +3439,10 @@ int dipToPx(int dip) { return (int) (displayMetrics.density * dip + 0.5f); } + private boolean isNoVoteWindowType() { + return NO_VOTE_WINDOW_TYPES.contains(mWindowAttributes.type); + } + private void performTraversals() { mLastPerformTraversalsSkipDrawReason = null; @@ -3767,9 +3791,7 @@ private void performTraversals() { if (surfaceControlChanged && mDisplayDecorationCached) { updateDisplayDecoration(); } - if (surfaceControlChanged - && mWindowAttributes.type - == WindowManager.LayoutParams.TYPE_STATUS_BAR) { + if (surfaceControlChanged && isNoVoteWindowType()) { mTransaction.setDefaultFrameRateCompatibility(mSurfaceControl, Surface.FRAME_RATE_COMPATIBILITY_NO_VOTE).apply(); } @@ -8024,6 +8046,11 @@ private int processPointerEvent(QueuedInputEvent q) { mLastClickToolType = event.getToolType(event.getActionIndex()); } + if (event.getPointerCount() == 3 && isSwipeToScreenshotGestureActive()) { + event.setAction(MotionEvent.ACTION_CANCEL); + Log.d("SwipeToScreenShot", "canceling motionEvent because of threeGesture detecting"); + } + mAttachInfo.mUnbufferedDispatchRequested = false; mAttachInfo.mHandlingPointerEvent = true; // If the event was fully handled by the handwriting initiator, then don't dispatch it @@ -13337,6 +13364,16 @@ private void updateInfrequentCount() { ? infrequentUpdateCount : infrequentUpdateCount + 1; } else { mInfrequentUpdateCount = 0; + + } + } + + private boolean isSwipeToScreenshotGestureActive() { + try { + return ActivityManager.getService().isSwipeToScreenshotGestureActive(); + } catch (RemoteException e) { + Log.e("SwipeToScreenshot", "isSwipeToScreenshotGestureActive exception", e); + return false; } } } diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 0582afe6655d..bb91bb468622 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; +import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import android.annotation.ColorInt; @@ -52,6 +53,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.provider.Settings; import android.transition.Scene; import android.transition.Transition; import android.transition.TransitionManager; @@ -1303,6 +1305,10 @@ public void clearFlags(int flags) { * @see #clearFlags */ public void setFlags(int flags, int mask) { + if ((mask & FLAG_SECURE) != 0 && Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.WINDOW_IGNORE_SECURE, 0) == 1) { + mask &= ~FLAG_SECURE; + } final WindowManager.LayoutParams attrs = getAttributes(); attrs.flags = (attrs.flags&~mask) | (flags&mask); mForcedWindowFlags |= mask; diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 5b39f62db261..6441b27d9015 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -882,6 +882,12 @@ public interface KeyboardShortcutsReceiver { */ int TAKE_SCREENSHOT_FULLSCREEN = 1; + /** + * Invoke screenshot flow allowing the user to select a region. + * @hide + */ + int TAKE_SCREENSHOT_SELECTED_REGION = 2; + /** * Invoke screenshot flow with an image provided by the caller. * @hide @@ -894,6 +900,7 @@ public interface KeyboardShortcutsReceiver { * @hide */ @IntDef({TAKE_SCREENSHOT_FULLSCREEN, + TAKE_SCREENSHOT_SELECTED_REGION, TAKE_SCREENSHOT_PROVIDED_IMAGE}) @interface ScreenshotType {} diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 79ecfe1e9141..3814b289a852 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -340,7 +340,7 @@ public final class AutofillManager { /** @hide */ public static final int RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1; /** @hide */ - public static final int DEFAULT_LOGGING_LEVEL = Build.IS_DEBUGGABLE + public static final int DEFAULT_LOGGING_LEVEL = Build.IS_ENG ? AutofillManager.FLAG_ADD_CLIENT_DEBUG : AutofillManager.NO_LOGGING; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 3c854ea4fad4..f630127a13e9 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -697,6 +697,7 @@ public abstract class AbsListView extends AdapterView implements Te private int mMinimumVelocity; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124051740) private int mMaximumVelocity; + private int mDecacheThreshold; private float mVelocityScale = 1.0f; final boolean[] mIsScrap = new boolean[1]; @@ -1011,6 +1012,7 @@ private void initAbsListView() { mVerticalScrollFactor = configuration.getScaledVerticalScrollFactor(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + mDecacheThreshold = mMaximumVelocity / 2; mOverscrollDistance = configuration.getScaledOverscrollDistance(); mOverflingDistance = configuration.getScaledOverflingDistance(); @@ -4965,7 +4967,7 @@ public void run() { // Keep the fling alive a little longer postDelayed(this, FLYWHEEL_TIMEOUT); } else { - endFling(); + endFling(false); // Don't disable the scrolling cache right after it was enabled mTouchMode = TOUCH_MODE_SCROLL; reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); } @@ -4976,6 +4978,7 @@ public void run() { FlingRunnable() { mScroller = new OverScroller(getContext()); + mScroller.setFriction(0.006f); } float getSplineFlingDistance(int velocity) { @@ -4985,6 +4988,11 @@ float getSplineFlingDistance(int velocity) { // Use AbsListView#fling(int) instead @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) void start(int initialVelocity) { + if (Math.abs(initialVelocity) > mDecacheThreshold) { + // For long flings, scrolling cache causes stutter, so don't use it + clearScrollingCache(); + } + int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0; mLastFlingY = initialY; mScroller.setInterpolator(null); @@ -5065,6 +5073,10 @@ void startScroll(int distance, int duration, boolean linear, // To interrupt a fling early you should use smoothScrollBy(0,0) instead @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) void endFling() { + endFling(true); + } + + void endFling(boolean clearCache) { mTouchMode = TOUCH_MODE_REST; removeCallbacks(this); @@ -5073,7 +5085,8 @@ void endFling() { if (!mSuppressIdleStateChangeCall) { reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } - clearScrollingCache(); + if (clearCache) + clearScrollingCache(); mScroller.abortAnimation(); if (mFlingStrictSpan != null) { diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index 6281ee9d05d1..3946fb621720 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -1013,7 +1013,15 @@ private void trackTouchEvent(MotionEvent event) { progress += scale * range + getMin(); setHotspot(x, y); - setProgressInternal(Math.round(progress), true, false); + setProgressInternal(updateTouchProgress(getProgress(), + Math.round(progress)), true, false); + } + + /** + * @hide + */ + protected int updateTouchProgress(int lastProgress, int newProgress) { + return newProgress; } /** diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index d54addbbcb8d..376707deaa72 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -1531,6 +1531,7 @@ private PopupBackgroundView createBackgroundView(View contentView) { final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams( MATCH_PARENT, height); backgroundView.addView(contentView, listParams); + backgroundView.setClipToOutline(true); return backgroundView; } diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 511c832a4876..66456ae65578 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -366,6 +366,7 @@ public int getMaxScrollAmount() { private void initScrollView() { mScroller = new OverScroller(getContext()); + mScroller.setFriction(0.006f); setFocusable(true); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); setWillNotDraw(false); diff --git a/core/java/com/android/internal/app/MediaRouteDialogPresenter.java b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java index 5628b7ed9d15..b19656e218cb 100644 --- a/core/java/com/android/internal/app/MediaRouteDialogPresenter.java +++ b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java @@ -89,7 +89,8 @@ public static Dialog createDialog(Context context, int routeTypes, final MediaRouter router = context.getSystemService(MediaRouter.class); MediaRouter.RouteInfo route = router.getSelectedRoute(); - if (route.isDefault() || !route.matchesTypes(routeTypes)) { + if (route.isDefault() || !route.matchesTypes(routeTypes) + || route.getStatusCode() == MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE) { final MediaRouteChooserDialog d = new MediaRouteChooserDialog(context, theme, showProgressBarWhenEmpty); d.setRouteTypes(routeTypes); diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index d474c6db4f02..a8d2780edd7a 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -636,7 +636,7 @@ private void finish() { // to ignore it for now. if (!mSurfaceOnly && !info.hwuiCallbackFired) { markEvent("FT#MissedHWUICallback", info.frameVsyncId); - Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId + Log.v(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId + ", CUJ=" + name); } } @@ -644,7 +644,7 @@ private void finish() { maxFrameTimeNanos = Math.max(info.totalDurationNanos, maxFrameTimeNanos); if (!info.surfaceControlCallbackFired) { markEvent("FT#MissedSFCallback", info.frameVsyncId); - Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId + Log.v(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId + ", CUJ=" + name); } } diff --git a/core/java/com/android/internal/lineage/app/LineageContextConstants.java b/core/java/com/android/internal/lineage/app/LineageContextConstants.java new file mode 100644 index 000000000000..13a9d0793712 --- /dev/null +++ b/core/java/com/android/internal/lineage/app/LineageContextConstants.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2015, The CyanogenMod 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.lineage.app; + +import android.annotation.SdkConstant; + +/** + * @hide + * TODO: We need to somehow make these managers accessible via getSystemService + */ +public final class LineageContextConstants { + + /** + * @hide + */ + private LineageContextConstants() { + // Empty constructor + } + + /** + * Use with {@link android.content.Context#getSystemService} to retrieve a + * {@link com.android.internal.lineage.LineageHardwareManager} to manage the extended + * hardware features of the device. + * + * @see android.content.Context#getSystemService + * @see com.android.internal.lineage.LineageHardwareManager + * + * @hide + */ + public static final String LINEAGE_HARDWARE_SERVICE = "lineagehardware"; + + /** + * Manages display color adjustments + * + * @hide + */ + public static final String LINEAGE_LIVEDISPLAY_SERVICE = "lineagelivedisplay"; + + /** + * Use with {@link android.content.Context#getSystemService} to retrieve a + * {@link lineageos.health.HealthInterface} to access the Health interface. + * + * @see android.content.Context#getSystemService + * @see lineageos.health.HealthInterface + * + * @hide + */ + public static final String LINEAGE_HEALTH_INTERFACE = "lineagehealth"; + +} diff --git a/core/java/com/android/internal/lineage/hardware/AdaptiveBacklight.java b/core/java/com/android/internal/lineage/hardware/AdaptiveBacklight.java new file mode 100644 index 000000000000..ad005d2e0ea1 --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/AdaptiveBacklight.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * Copyright (C) 2018 The LineageOS 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.lineage.hardware; + +import android.util.Log; + +import com.android.internal.util.custom.FileUtils; + +/** + * Adaptive backlight support (this refers to technologies like NVIDIA SmartDimmer, + * QCOM CABL or Samsung CABC). + */ +public class AdaptiveBacklight { + + private static final String TAG = "AdaptiveBacklight"; + + private static final String FILE_CABC = "/sys/class/graphics/fb0/cabc"; + + /** + * Whether device supports an adaptive backlight technology. + * + * @return boolean Supported devices must return always true + */ + public static boolean isSupported() { + return FileUtils.isFileReadable(FILE_CABC) && FileUtils.isFileWritable(FILE_CABC); + } + + /** + * This method return the current activation status of the adaptive backlight technology. + * + * @return boolean Must be false when adaptive backlight is not supported or not activated, or + * the operation failed while reading the status; true in any other case. + */ + public static boolean isEnabled() { + return Integer.parseInt(FileUtils.readOneLine(FILE_CABC)) > 0; + } + + /** + * This method allows to setup adaptive backlight technology status. + * + * @param status The new adaptive backlight status + * @return boolean Must be false if adaptive backlight is not supported or the operation + * failed; true in any other case. + */ + public static boolean setEnabled(boolean status) { + return FileUtils.writeLine(FILE_CABC, status ? "1" : "0"); + } +} diff --git a/core/java/com/android/internal/lineage/hardware/AutoContrast.java b/core/java/com/android/internal/lineage/hardware/AutoContrast.java new file mode 100644 index 000000000000..38e03cbcc282 --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/AutoContrast.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2014 The CyanogenMod 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.lineage.hardware; + +import com.android.internal.util.custom.FileUtils; + +import android.util.Log; + +/** + * Auto Contrast Optimization + */ +public class AutoContrast { + + private static final String TAG = "AutoContrast"; + + private static final String FILE_ACO = "/sys/class/graphics/fb0/aco"; + + /** + * Whether device supports ACO + * + * @return boolean Supported devices must return always true + */ + public static boolean isSupported() { + return FileUtils.isFileReadable(FILE_ACO) && FileUtils.isFileWritable(FILE_ACO); + } + + /** + * This method return the current activation status of ACO + * + * @return boolean Must be false when ACO is not supported or not activated, or + * the operation failed while reading the status; true in any other case. + */ + public static boolean isEnabled() { + try { + return Integer.parseInt(FileUtils.readOneLine(FILE_ACO)) > 0; + } catch (Exception e) { + Log.e(TAG, e.getMessage(), e); + } + return false; + } + + /** + * This method allows to setup ACO + * + * @param status The new ACO status + * @return boolean Must be false if ACO is not supported or the operation + * failed; true in any other case. + */ + public static boolean setEnabled(boolean status) { + return FileUtils.writeLine(FILE_ACO, status ? "1" : "0"); + } + + /** + * Whether adaptive backlight (CABL / CABC) is required to be enabled + * + * @return boolean False if adaptive backlight is not a dependency + */ + public static boolean isAdaptiveBacklightRequired() { + return false; + } +} diff --git a/core/java/com/android/internal/lineage/hardware/ColorBalance.java b/core/java/com/android/internal/lineage/hardware/ColorBalance.java new file mode 100644 index 000000000000..96d748e088dc --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/ColorBalance.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * Copyright (C) 2018 The LineageOS 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.lineage.hardware; + +/** + * Color balance support + * + * Color balance controls allow direct adjustment of display color temperature + * using a range of values. A zero implies no adjustment, negative values + * move towards warmer temperatures, and positive values move towards + * cool temperatures. + */ +public class ColorBalance { + + /** + * Whether device supports color balance control + * + * @return boolean Supported devices must return always true + */ + public static boolean isSupported() { + return false; + } + + /** + * This method returns the current color balance value + * + * @return int Zero when no adjustment is made, negative values move + * towards warmer temperatures, positive values move towards cooler temperatures. + */ + public static int getValue() { + return 0; + } + + /** + * This method allows to set the display color balance + * + * @param value + * @return boolean Must be false if feature is not supported or the operation + * failed; true in any other case. + */ + public static boolean setValue(int value) { + return false; + } + + /** + * Get the minimum allowed color adjustment value + * @return int + */ + public static int getMinValue() { + return 0; + } + + /** + * Get the maximum allowed color adjustment value + * @return int + */ + public static int getMaxValue() { + return 0; + } +} diff --git a/core/java/com/android/internal/lineage/hardware/ColorEnhancement.java b/core/java/com/android/internal/lineage/hardware/ColorEnhancement.java new file mode 100644 index 000000000000..889e44f31118 --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/ColorEnhancement.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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.lineage.hardware; + +import com.android.internal.util.custom.FileUtils; + +import android.util.Log; + +/** + * Color enhancement support + */ +public class ColorEnhancement { + + private static final String TAG = "ColorEnhancement"; + + private static final String FILE_CE = "/sys/class/graphics/fb0/color_enhance"; + + /** + * Whether device supports an color enhancement technology. + * + * @return boolean Supported devices must return always true + */ + public static boolean isSupported() { + return FileUtils.isFileReadable(FILE_CE) && FileUtils.isFileWritable(FILE_CE); + } + + /** + * This method return the current activation status of the color enhancement technology. + * + * @return boolean Must be false when color enhancement is not supported or not activated, or + * the operation failed while reading the status; true in any other case. + */ + public static boolean isEnabled() { + try { + return Integer.parseInt(FileUtils.readOneLine(FILE_CE)) > 0; + } catch (Exception e) { + Log.e(TAG, e.getMessage(), e); + } + return false; + } + + /** + * This method allows to setup color enhancement technology status. + * + * @param status The new color enhancement status + * @return boolean Must be false if adaptive backlight is not supported or the operation + * failed; true in any other case. + */ + public static boolean setEnabled(boolean status) { + return FileUtils.writeLine(FILE_CE, status ? "1" : "0"); + } +} diff --git a/core/java/com/android/internal/lineage/hardware/DisplayColorCalibration.java b/core/java/com/android/internal/lineage/hardware/DisplayColorCalibration.java new file mode 100644 index 000000000000..e27cb9fe0ade --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/DisplayColorCalibration.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * Copyright (C) 2018 The LineageOS 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.lineage.hardware; + +import com.android.internal.util.custom.FileUtils; + +public class DisplayColorCalibration { + + private static final String TAG = "DisplayColorCalibration"; + + private static final String COLOR_FILE = "/sys/class/graphics/fb0/rgb"; + + private static final int MIN = 255; + private static final int MAX = 32768; + + public static boolean isSupported() { + return FileUtils.isFileReadable(COLOR_FILE) && FileUtils.isFileWritable(COLOR_FILE); + } + + public static int getMaxValue() { + return MAX; + } + + public static int getMinValue() { + return MIN; + } + + public static int getDefValue() { + return getMaxValue(); + } + + public static String getCurColors() { + return FileUtils.readOneLine(COLOR_FILE); + } + + public static boolean setColors(String colors) { + return FileUtils.writeLine(COLOR_FILE, colors); + } + +} diff --git a/core/java/com/android/internal/lineage/hardware/DisplayMode.aidl b/core/java/com/android/internal/lineage/hardware/DisplayMode.aidl new file mode 100644 index 000000000000..bc44763f0f76 --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/DisplayMode.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2015 The CyanogenMod 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.lineage.hardware; + +parcelable DisplayMode; diff --git a/core/java/com/android/internal/lineage/hardware/DisplayMode.java b/core/java/com/android/internal/lineage/hardware/DisplayMode.java new file mode 100644 index 000000000000..8a72d124e1b3 --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/DisplayMode.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2015 The CyanogenMod 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.lineage.hardware; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.lineage.util.Concierge; +import com.android.internal.lineage.util.Concierge.ParcelInfo; + +/** + * Display Modes API + * + * A device may implement a list of preset display modes for different + * viewing intents, such as movies, photos, or extra vibrance. These + * modes may have multiple components such as gamma correction, white + * point adjustment, etc, but are activated by a single control point. + * + * This API provides support for enumerating and selecting the + * modes supported by the hardware. + * + * A DisplayMode is referenced by it's identifier and carries an + * associated name (up to the user to translate this value). + */ +public class DisplayMode implements Parcelable { + public final int id; + public final String name; + + public DisplayMode(int id, String name) { + this.id = id; + this.name = name; + } + + private DisplayMode(Parcel parcel) { + // Read parcelable version via the Concierge + ParcelInfo parcelInfo = Concierge.receiveParcel(parcel); + int parcelableVersion = parcelInfo.getParcelVersion(); + + // temp vars + int tmpId = -1; + String tmpName = null; + + tmpId = parcel.readInt(); + if (parcel.readInt() != 0) { + tmpName = parcel.readString(); + } + + // set temps + this.id = tmpId; + this.name = tmpName; + + // Complete parcel info for the concierge + parcelInfo.complete(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + // Tell the concierge to prepare the parcel + ParcelInfo parcelInfo = Concierge.prepareParcel(out); + + out.writeInt(id); + if (name != null) { + out.writeInt(1); + out.writeString(name); + } else { + out.writeInt(0); + } + + // Complete the parcel info for the concierge + parcelInfo.complete(); + } + + /** @hide */ + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public DisplayMode createFromParcel(Parcel in) { + return new DisplayMode(in); + } + + @Override + public DisplayMode[] newArray(int size) { + return new DisplayMode[size]; + } + }; + +} diff --git a/core/java/com/android/internal/lineage/hardware/DisplayModeControl.java b/core/java/com/android/internal/lineage/hardware/DisplayModeControl.java new file mode 100644 index 000000000000..1489d8fffd8e --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/DisplayModeControl.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * Copyright (C) 2018 The LineageOS 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.lineage.hardware; + +/** + * Display Modes API + * + * A device may implement a list of preset display modes for different + * viewing intents, such as movies, photos, or extra vibrance. These + * modes may have multiple components such as gamma correction, white + * point adjustment, etc, but are activated by a single control point. + * + * This API provides support for enumerating and selecting the + * modes supported by the hardware. + */ + +public class DisplayModeControl { + + /** + * All HAF classes should export this boolean. + * Real implementations must, of course, return true + */ + public static boolean isSupported() { + return false; + } + + /** + * Get the list of available modes. A mode has an integer + * identifier and a string name. + * + * It is the responsibility of the upper layers to + * map the name to a human-readable format or perform translation. + */ + public static DisplayMode[] getAvailableModes() { + return new DisplayMode[0]; + } + + /** + * Get the name of the currently selected mode. This can return + * null if no mode is selected. + */ + public static DisplayMode getCurrentMode() { + return null; + } + + /** + * Selects a mode from the list of available modes by it's + * string identifier. Returns true on success, false for + * failure. It is up to the implementation to determine + * if this mode is valid. + */ + public static boolean setMode(DisplayMode mode, boolean makeDefault) { + return false; + } + + /** + * Gets the preferred default mode for this device by it's + * string identifier. Can return null if there is no default. + */ + public static DisplayMode getDefaultMode() { + return null; + } +} diff --git a/core/java/com/android/internal/lineage/hardware/HIDLHelper.java b/core/java/com/android/internal/lineage/hardware/HIDLHelper.java new file mode 100644 index 000000000000..e512478b5829 --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/HIDLHelper.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 The LineageOS 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.lineage.hardware; + +import android.util.Range; + +import com.android.internal.lineage.hardware.DisplayMode; +import com.android.internal.lineage.hardware.HSIC; + +import java.util.ArrayList; + +class HIDLHelper { + + static TouchscreenGesture[] fromHIDLGestures( + ArrayList gestures) { + int size = gestures.size(); + TouchscreenGesture[] r = new TouchscreenGesture[size]; + for (int i = 0; i < size; i++) { + vendor.lineage.touch.V1_0.Gesture g = gestures.get(i); + r[i] = new TouchscreenGesture(g.id, g.name, g.keycode); + } + return r; + } + + static vendor.lineage.touch.V1_0.Gesture toHIDLGesture(TouchscreenGesture gesture) { + vendor.lineage.touch.V1_0.Gesture g = new vendor.lineage.touch.V1_0.Gesture(); + g.id = gesture.id; + g.name = gesture.name; + g.keycode = gesture.keycode; + return g; + } + + static DisplayMode[] fromHIDLModes( + ArrayList modes) { + int size = modes.size(); + DisplayMode[] r = new DisplayMode[size]; + for (int i = 0; i < size; i++) { + vendor.lineage.livedisplay.V2_0.DisplayMode m = modes.get(i); + r[i] = new DisplayMode(m.id, m.name); + } + return r; + } + + static DisplayMode fromHIDLMode( + vendor.lineage.livedisplay.V2_0.DisplayMode mode) { + return new DisplayMode(mode.id, mode.name); + } + + static HSIC fromHIDLHSIC(vendor.lineage.livedisplay.V2_0.HSIC hsic) { + return new HSIC(hsic.hue, hsic.saturation, hsic.intensity, + hsic.contrast, hsic.saturationThreshold); + } + + static vendor.lineage.livedisplay.V2_0.HSIC toHIDLHSIC(HSIC hsic) { + vendor.lineage.livedisplay.V2_0.HSIC h = new vendor.lineage.livedisplay.V2_0.HSIC(); + h.hue = hsic.getHue(); + h.saturation = hsic.getSaturation(); + h.intensity = hsic.getIntensity(); + h.contrast = hsic.getContrast(); + h.saturationThreshold = hsic.getSaturationThreshold(); + return h; + } + + static Range fromHIDLRange(vendor.lineage.livedisplay.V2_0.Range range) { + return new Range(range.min, range.max); + } + + static Range fromHIDLRange(vendor.lineage.livedisplay.V2_0.FloatRange range) { + return new Range(range.min, range.max); + } + +} diff --git a/core/java/com/android/internal/lineage/hardware/HSIC.aidl b/core/java/com/android/internal/lineage/hardware/HSIC.aidl new file mode 100644 index 000000000000..e8b2e4d20522 --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/HSIC.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2015 The CyanogenMod 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.lineage.hardware; + +parcelable HSIC; diff --git a/core/java/com/android/internal/lineage/hardware/HSIC.java b/core/java/com/android/internal/lineage/hardware/HSIC.java new file mode 100644 index 000000000000..d7531a68206c --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/HSIC.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2016 The CyanogenMod 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.lineage.hardware; + +import android.graphics.Color; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.Locale; + +public class HSIC implements Parcelable { + + private final float mHue; + private final float mSaturation; + private final float mIntensity; + private final float mContrast; + private final float mSaturationThreshold; + + public HSIC(float hue, float saturation, float intensity, + float contrast, float saturationThreshold) { + mHue = hue; + mSaturation = saturation; + mIntensity = intensity; + mContrast = contrast; + mSaturationThreshold = saturationThreshold; + } + + public float getHue() { + return mHue; + } + + public float getSaturation() { + return mSaturation; + } + + public float getIntensity() { + return mIntensity; + } + + public float getContrast() { + return mContrast; + } + + public float getSaturationThreshold() { + return mSaturationThreshold; + } + + public String flatten() { + return String.format(Locale.US, "%f|%f|%f|%f|%f", mHue, mSaturation, + mIntensity, mContrast, mSaturationThreshold); + } + + public static HSIC unflattenFrom(String flat) throws NumberFormatException { + final String[] unflat = TextUtils.split(flat, "\\|"); + if (unflat.length != 4 && unflat.length != 5) { + throw new NumberFormatException("Failed to unflatten HSIC values: " + flat); + } + return new HSIC(Float.parseFloat(unflat[0]), Float.parseFloat(unflat[1]), + Float.parseFloat(unflat[2]), Float.parseFloat(unflat[3]), + unflat.length == 5 ? Float.parseFloat(unflat[4]) : 0.0f); + } + + public int[] toRGB() { + final int c = Color.HSVToColor(toFloatArray()); + return new int[] { Color.red(c), Color.green(c), Color.blue(c) }; + } + + public float[] toFloatArray() { + return new float[] { mHue, mSaturation, mIntensity, mContrast, mSaturationThreshold }; + } + + public static HSIC fromFloatArray(float[] hsic) { + if (hsic.length == 5) { + return new HSIC(hsic[0], hsic[1], hsic[2], hsic[3], hsic[4]); + } else if (hsic.length == 4) { + return new HSIC(hsic[0], hsic[1], hsic[2], hsic[3], 0.0f); + } + return null; + } + + @Override + public String toString() { + return String.format(Locale.US, "HSIC={ hue=%f saturation=%f intensity=%f " + + "contrast=%f saturationThreshold=%f }", + mHue, mSaturation, mIntensity, mContrast, mSaturationThreshold); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeFloatArray(toFloatArray()); + } + + /** + * @hide + */ + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public HSIC createFromParcel(Parcel in) { + float[] fromParcel = new float[5]; + in.readFloatArray(fromParcel); + return HSIC.fromFloatArray(fromParcel); + } + + @Override + public HSIC[] newArray(int size) { + return new HSIC[size]; + } + }; +}; diff --git a/core/java/com/android/internal/lineage/hardware/ILineageHardwareService.aidl b/core/java/com/android/internal/lineage/hardware/ILineageHardwareService.aidl new file mode 100644 index 000000000000..d25dbb98e180 --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/ILineageHardwareService.aidl @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2015-2016 The CyanogenMod Project + * 2017-2018 The LineageOS 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.lineage.hardware; + +import com.android.internal.lineage.hardware.DisplayMode; +import com.android.internal.lineage.hardware.HSIC; + +/** @hide */ +interface ILineageHardwareService { + + int getSupportedFeatures(); + boolean get(int feature); + boolean set(int feature, boolean enable); + + int[] getDisplayColorCalibration(); + boolean setDisplayColorCalibration(in int[] rgb); + + boolean requireAdaptiveBacklightForSunlightEnhancement(); + + DisplayMode[] getDisplayModes(); + DisplayMode getCurrentDisplayMode(); + DisplayMode getDefaultDisplayMode(); + boolean setDisplayMode(in DisplayMode mode, boolean makeDefault); + + boolean isSunlightEnhancementSelfManaged(); + + int getColorBalanceMin(); + int getColorBalanceMax(); + int getColorBalance(); + boolean setColorBalance(int value); + + HSIC getPictureAdjustment(); + HSIC getDefaultPictureAdjustment(); + boolean setPictureAdjustment(in HSIC hsic); + float[] getPictureAdjustmentRanges(); +} diff --git a/core/java/com/android/internal/lineage/hardware/ILiveDisplayService.aidl b/core/java/com/android/internal/lineage/hardware/ILiveDisplayService.aidl new file mode 100644 index 000000000000..57a2302756e4 --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/ILiveDisplayService.aidl @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2016, The CyanogenMod Project + * 2021 The LineageOS 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.lineage.hardware; + +import com.android.internal.lineage.hardware.HSIC; +import com.android.internal.lineage.hardware.LiveDisplayConfig; + +/** @hide */ +interface ILiveDisplayService { + LiveDisplayConfig getConfig(); + + int getMode(); + boolean setMode(int mode); + + float[] getColorAdjustment(); + boolean setColorAdjustment(in float[] adj); + + boolean isAutoContrastEnabled(); + boolean setAutoContrastEnabled(boolean enabled); + + boolean isCABCEnabled(); + boolean setCABCEnabled(boolean enabled); + + boolean isColorEnhancementEnabled(); + boolean setColorEnhancementEnabled(boolean enabled); + + int getDayColorTemperature(); + boolean setDayColorTemperature(int temperature); + + int getNightColorTemperature(); + boolean setNightColorTemperature(int temperature); + + int getColorTemperature(); + + boolean isAutomaticOutdoorModeEnabled(); + boolean setAutomaticOutdoorModeEnabled(boolean enabled); + + HSIC getPictureAdjustment(); + HSIC getDefaultPictureAdjustment(); + boolean setPictureAdjustment(in HSIC adj); + boolean isNight(); + + boolean isAntiFlickerEnabled(); + boolean setAntiFlickerEnabled(boolean enabled); +} diff --git a/core/java/com/android/internal/lineage/hardware/LineageHardwareManager.java b/core/java/com/android/internal/lineage/hardware/LineageHardwareManager.java new file mode 100644 index 000000000000..f17bd6aa4082 --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/LineageHardwareManager.java @@ -0,0 +1,879 @@ +/* + * Copyright (C) 2015-2016 The CyanogenMod Project + * 2017-2021 The LineageOS 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.lineage.hardware; + +import android.content.Context; +import android.hidl.base.V1_0.IBase; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Range; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +import com.android.internal.lineage.app.LineageContextConstants; +import com.android.internal.lineage.hardware.DisplayMode; +import com.android.internal.lineage.hardware.HIDLHelper; +import com.android.internal.lineage.hardware.HSIC; + +import vendor.lineage.livedisplay.V2_0.IAdaptiveBacklight; +import vendor.lineage.livedisplay.V2_0.IAutoContrast; +import vendor.lineage.livedisplay.V2_0.IColorBalance; +import vendor.lineage.livedisplay.V2_0.IColorEnhancement; +import vendor.lineage.livedisplay.V2_0.IDisplayColorCalibration; +import vendor.lineage.livedisplay.V2_0.IDisplayModes; +import vendor.lineage.livedisplay.V2_0.IPictureAdjustment; +import vendor.lineage.livedisplay.V2_0.IReadingEnhancement; +import vendor.lineage.livedisplay.V2_0.ISunlightEnhancement; +import vendor.lineage.livedisplay.V2_1.IAntiFlicker; +import vendor.lineage.touch.V1_0.IGloveMode; +import vendor.lineage.touch.V1_0.IHighTouchPollingRate; +import vendor.lineage.touch.V1_0.IKeyDisabler; +import vendor.lineage.touch.V1_0.IStylusMode; +import vendor.lineage.touch.V1_0.ITouchscreenGesture; + +import java.io.UnsupportedEncodingException; +import java.lang.IllegalArgumentException; +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Manages access to LineageOS hardware extensions + * + *

    + * This manager requires the HARDWARE_ABSTRACTION_ACCESS permission. + *

    + * To get the instance of this class, utilize LineageHardwareManager#getInstance(Context context) + */ +public final class LineageHardwareManager { + private static final String TAG = "LineageHardwareManager"; + + // The VisibleForTesting annotation is to ensure Proguard doesn't remove these + // fields, as they might be used via reflection. When the @Keep annotation in + // the support library is properly handled in the platform, we should change this. + + /** + * High Touch Polling Rate + */ + @VisibleForTesting + public static final int FEATURE_HIGH_TOUCH_POLLING_RATE = 0x8; + + /** + * High touch sensitivity for touch panels + */ + @VisibleForTesting + public static final int FEATURE_HIGH_TOUCH_SENSITIVITY = 0x10; + + /** + * Hardware navigation key disablement + */ + @VisibleForTesting + public static final int FEATURE_KEY_DISABLE = 0x20; + + /** + * Touchscreen hovering + */ + @VisibleForTesting + public static final int FEATURE_TOUCH_HOVERING = 0x800; + + /** + * Touchscreen gesture + */ + @VisibleForTesting + public static final int FEATURE_TOUCHSCREEN_GESTURES = 0x80000; + + /** + * Adaptive backlight support (this refers to technologies like NVIDIA SmartDimmer, + * QCOM CABL or Samsung CABC) + */ + @VisibleForTesting + public static final int FEATURE_ADAPTIVE_BACKLIGHT = 0x1; + + /** + * Color enhancement support + */ + @VisibleForTesting + public static final int FEATURE_COLOR_ENHANCEMENT = 0x2; + + /** + * Display RGB color calibration + */ + @VisibleForTesting + public static final int FEATURE_DISPLAY_COLOR_CALIBRATION = 0x4; + + /** + * Increased display readability in bright light + */ + @VisibleForTesting + public static final int FEATURE_SUNLIGHT_ENHANCEMENT = 0x100; + + /** + * Auto contrast + */ + @VisibleForTesting + public static final int FEATURE_AUTO_CONTRAST = 0x1000; + + /** + * Display modes + */ + @VisibleForTesting + public static final int FEATURE_DISPLAY_MODES = 0x2000; + + /** + * Reading mode + */ + @VisibleForTesting + public static final int FEATURE_READING_ENHANCEMENT = 0x4000; + + /** + * Color balance + */ + @VisibleForTesting + public static final int FEATURE_COLOR_BALANCE = 0x20000; + + /** + * HSIC picture adjustment + */ + @VisibleForTesting + public static final int FEATURE_PICTURE_ADJUSTMENT = 0x40000; + + /** + * Anti flicker mode + */ + @VisibleForTesting + public static final int FEATURE_ANTI_FLICKER = 0x200000; + + private static final List BOOLEAN_FEATURES = Arrays.asList( + FEATURE_HIGH_TOUCH_POLLING_RATE, + FEATURE_HIGH_TOUCH_SENSITIVITY, + FEATURE_KEY_DISABLE, + FEATURE_TOUCH_HOVERING, + FEATURE_ADAPTIVE_BACKLIGHT, + FEATURE_ANTI_FLICKER, + FEATURE_AUTO_CONTRAST, + FEATURE_COLOR_ENHANCEMENT, + FEATURE_SUNLIGHT_ENHANCEMENT, + FEATURE_READING_ENHANCEMENT + ); + + private static ILineageHardwareService sService; + private static LineageHardwareManager sLineageHardwareManagerInstance; + + private Context mContext; + + private final ArrayMap mDisplayModeMappings = new ArrayMap(); + private final boolean mFilterDisplayModes; + + // HIDL hals + private HashMap mHIDLMap = new HashMap(); + + /** + * @hide to prevent subclassing from outside of the framework + */ + private LineageHardwareManager(Context context) { + Context appContext = context.getApplicationContext(); + if (appContext != null) { + mContext = appContext; + } else { + mContext = context; + } + sService = getService(); + + if (!checkService()) { + Log.wtf(TAG, "Unable to get LineageHardwareService. The service either" + + " crashed, was not started, or the interface has been called to early in" + + " SystemServer init"); + } + + final String[] mappings = mContext.getResources().getStringArray( + com.android.internal.R.array.config_displayModeMappings); + if (mappings != null && mappings.length > 0) { + for (String mapping : mappings) { + String[] split = mapping.split(":"); + if (split.length == 2) { + mDisplayModeMappings.put(split[0], split[1]); + } + } + } + mFilterDisplayModes = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_filterDisplayModes); + } + + /** + * Get or create an instance of the {@link com.android.internal.lineage.hardware.LineageHardwareManager} + * @param context + * @return {@link LineageHardwareManager} + */ + public static LineageHardwareManager getInstance(Context context) { + if (sLineageHardwareManagerInstance == null) { + sLineageHardwareManagerInstance = new LineageHardwareManager(context); + } + return sLineageHardwareManagerInstance; + } + + /** @hide */ + public static ILineageHardwareService getService() { + if (sService != null) { + return sService; + } + IBinder b = ServiceManager.getService(LineageContextConstants.LINEAGE_HARDWARE_SERVICE); + if (b != null) { + sService = ILineageHardwareService.Stub.asInterface(b); + return sService; + } + return null; + } + + /** + * Determine if a Lineage Hardware feature is supported on this device + * + * @param feature The Lineage Hardware feature to query + * + * @return true if the feature is supported, false otherwise. + */ + public boolean isSupported(int feature) { + return isSupportedHIDL(feature) || isSupportedLegacy(feature); + } + + private boolean isSupportedHIDL(int feature) { + if (!mHIDLMap.containsKey(feature)) { + mHIDLMap.put(feature, getHIDLService(feature)); + } + return mHIDLMap.get(feature) != null; + } + + private boolean isSupportedLegacy(int feature) { + try { + if (checkService()) { + return feature == (sService.getSupportedFeatures() & feature); + } + } catch (RemoteException e) { + } + return false; + } + + private IBase getHIDLService(int feature) { + try { + switch (feature) { + case FEATURE_HIGH_TOUCH_POLLING_RATE: + return IHighTouchPollingRate.getService(true); + case FEATURE_HIGH_TOUCH_SENSITIVITY: + return IGloveMode.getService(true); + case FEATURE_KEY_DISABLE: + return IKeyDisabler.getService(true); + case FEATURE_TOUCH_HOVERING: + return IStylusMode.getService(true); + case FEATURE_TOUCHSCREEN_GESTURES: + return ITouchscreenGesture.getService(true); + case FEATURE_ADAPTIVE_BACKLIGHT: + return IAdaptiveBacklight.getService(true); + case FEATURE_ANTI_FLICKER: + return IAntiFlicker.getService(true); + case FEATURE_AUTO_CONTRAST: + return IAutoContrast.getService(true); + case FEATURE_COLOR_BALANCE: + return IColorBalance.getService(true); + case FEATURE_COLOR_ENHANCEMENT: + return IColorEnhancement.getService(true); + case FEATURE_DISPLAY_COLOR_CALIBRATION: + return IDisplayColorCalibration.getService(true); + case FEATURE_DISPLAY_MODES: + return IDisplayModes.getService(true); + case FEATURE_PICTURE_ADJUSTMENT: + return IPictureAdjustment.getService(true); + case FEATURE_READING_ENHANCEMENT: + return IReadingEnhancement.getService(true); + case FEATURE_SUNLIGHT_ENHANCEMENT: + return ISunlightEnhancement.getService(true); + } + } catch (NoSuchElementException | RemoteException e) { + } + return null; + } + + /** + * String version for preference constraints + * + * @hide + */ + public boolean isSupported(String feature) { + if (!feature.startsWith("FEATURE_")) { + return false; + } + try { + Field f = getClass().getField(feature); + if (f != null) { + return isSupported((int) f.get(null)); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + Log.d(TAG, e.getMessage(), e); + } + + return false; + } + /** + * Determine if the given feature is enabled or disabled. + * + * Only used for features which have simple enable/disable controls. + * + * @param feature the Lineage Hardware feature to query + * + * @return true if the feature is enabled, false otherwise. + */ + public boolean get(int feature) { + if (!BOOLEAN_FEATURES.contains(feature)) { + throw new IllegalArgumentException(feature + " is not a boolean"); + } + + try { + if (isSupportedHIDL(feature)) { + IBase obj = mHIDLMap.get(feature); + switch (feature) { + case FEATURE_HIGH_TOUCH_POLLING_RATE: + IHighTouchPollingRate highTouchPollingRate = (IHighTouchPollingRate) obj; + return highTouchPollingRate.isEnabled(); + case FEATURE_HIGH_TOUCH_SENSITIVITY: + IGloveMode gloveMode = (IGloveMode) obj; + return gloveMode.isEnabled(); + case FEATURE_KEY_DISABLE: + IKeyDisabler keyDisabler = (IKeyDisabler) obj; + return keyDisabler.isEnabled(); + case FEATURE_TOUCH_HOVERING: + IStylusMode stylusMode = (IStylusMode) obj; + return stylusMode.isEnabled(); + case FEATURE_ADAPTIVE_BACKLIGHT: + IAdaptiveBacklight adaptiveBacklight = (IAdaptiveBacklight) obj; + return adaptiveBacklight.isEnabled(); + case FEATURE_ANTI_FLICKER: + IAntiFlicker antiFlicker = (IAntiFlicker) obj; + return antiFlicker.isEnabled(); + case FEATURE_AUTO_CONTRAST: + IAutoContrast autoContrast = (IAutoContrast) obj; + return autoContrast.isEnabled(); + case FEATURE_COLOR_ENHANCEMENT: + IColorEnhancement colorEnhancement = (IColorEnhancement) obj; + return colorEnhancement.isEnabled(); + case FEATURE_SUNLIGHT_ENHANCEMENT: + ISunlightEnhancement sunlightEnhancement = (ISunlightEnhancement) obj; + return sunlightEnhancement.isEnabled(); + case FEATURE_READING_ENHANCEMENT: + IReadingEnhancement readingEnhancement = (IReadingEnhancement) obj; + return readingEnhancement.isEnabled(); + } + } else if (checkService()) { + return sService.get(feature); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * Enable or disable the given feature + * + * Only used for features which have simple enable/disable controls. + * + * @param feature the Lineage Hardware feature to set + * @param enable true to enable, false to disale + * + * @return true if the feature is enabled, false otherwise. + */ + public boolean set(int feature, boolean enable) { + if (!BOOLEAN_FEATURES.contains(feature)) { + throw new IllegalArgumentException(feature + " is not a boolean"); + } + + try { + if (isSupportedHIDL(feature)) { + IBase obj = mHIDLMap.get(feature); + switch (feature) { + case FEATURE_HIGH_TOUCH_POLLING_RATE: + IHighTouchPollingRate highTouchPollingRate = (IHighTouchPollingRate) obj; + return highTouchPollingRate.setEnabled(enable); + case FEATURE_HIGH_TOUCH_SENSITIVITY: + IGloveMode gloveMode = (IGloveMode) obj; + return gloveMode.setEnabled(enable); + case FEATURE_KEY_DISABLE: + IKeyDisabler keyDisabler = (IKeyDisabler) obj; + return keyDisabler.setEnabled(enable); + case FEATURE_TOUCH_HOVERING: + IStylusMode stylusMode = (IStylusMode) obj; + return stylusMode.setEnabled(enable); + case FEATURE_ADAPTIVE_BACKLIGHT: + IAdaptiveBacklight adaptiveBacklight = (IAdaptiveBacklight) obj; + return adaptiveBacklight.setEnabled(enable); + case FEATURE_ANTI_FLICKER: + IAntiFlicker antiFlicker = (IAntiFlicker) obj; + return antiFlicker.setEnabled(enable); + case FEATURE_AUTO_CONTRAST: + IAutoContrast autoContrast = (IAutoContrast) obj; + return autoContrast.setEnabled(enable); + case FEATURE_COLOR_ENHANCEMENT: + IColorEnhancement colorEnhancement = (IColorEnhancement) obj; + return colorEnhancement.setEnabled(enable); + case FEATURE_SUNLIGHT_ENHANCEMENT: + ISunlightEnhancement sunlightEnhancement = (ISunlightEnhancement) obj; + return sunlightEnhancement.setEnabled(enable); + case FEATURE_READING_ENHANCEMENT: + IReadingEnhancement readingEnhancement = (IReadingEnhancement) obj; + return readingEnhancement.setEnabled(enable); + } + } else if (checkService()) { + return sService.set(feature, enable); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * @return a list of available touchscreen gestures on the devices + */ + public TouchscreenGesture[] getTouchscreenGestures() { + try { + if (isSupportedHIDL(FEATURE_TOUCHSCREEN_GESTURES)) { + ITouchscreenGesture touchscreenGesture = (ITouchscreenGesture) + mHIDLMap.get(FEATURE_TOUCHSCREEN_GESTURES); + return HIDLHelper.fromHIDLGestures(touchscreenGesture.getSupportedGestures()); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * @return true if setting the activation status was successful + */ + public boolean setTouchscreenGestureEnabled( + TouchscreenGesture gesture, boolean state) { + try { + if (isSupportedHIDL(FEATURE_TOUCHSCREEN_GESTURES)) { + ITouchscreenGesture touchscreenGesture = (ITouchscreenGesture) + mHIDLMap.get(FEATURE_TOUCHSCREEN_GESTURES); + return touchscreenGesture.setGestureEnabled( + HIDLHelper.toHIDLGesture(gesture), state); + } + } catch (RemoteException e) { + } + return false; + } + + private int getArrayValue(int[] arr, int idx, int defaultValue) { + if (arr == null || arr.length <= idx) { + return defaultValue; + } + + return arr[idx]; + } + + /** + * {@hide} + */ + public static final int COLOR_CALIBRATION_RED_INDEX = 0; + /** + * {@hide} + */ + public static final int COLOR_CALIBRATION_GREEN_INDEX = 1; + /** + * {@hide} + */ + public static final int COLOR_CALIBRATION_BLUE_INDEX = 2; + /** + * {@hide} + */ + public static final int COLOR_CALIBRATION_MIN_INDEX = 3; + /** + * {@hide} + */ + public static final int COLOR_CALIBRATION_MAX_INDEX = 4; + + private int[] getDisplayColorCalibrationArray() { + try { + if (isSupportedHIDL(FEATURE_DISPLAY_COLOR_CALIBRATION)) { + IDisplayColorCalibration displayColorCalibration = (IDisplayColorCalibration) + mHIDLMap.get(FEATURE_DISPLAY_COLOR_CALIBRATION); + return ArrayUtils.convertToIntArray(displayColorCalibration.getCalibration()); + } else if (checkService()) { + return sService.getDisplayColorCalibration(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * @return the current RGB calibration, where int[0] = R, int[1] = G, int[2] = B. + */ + public int[] getDisplayColorCalibration() { + int[] arr = getDisplayColorCalibrationArray(); + if (arr == null || arr.length < 3) { + return null; + } + return Arrays.copyOf(arr, 3); + } + + /** + * @return The minimum value for all colors + */ + public int getDisplayColorCalibrationMin() { + if (isSupportedHIDL(FEATURE_DISPLAY_COLOR_CALIBRATION)) { + IDisplayColorCalibration displayColorCalibration = (IDisplayColorCalibration) + mHIDLMap.get(FEATURE_DISPLAY_COLOR_CALIBRATION); + try { + return displayColorCalibration.getMinValue(); + } catch (RemoteException e) { + return 0; + } + } + + return getArrayValue(getDisplayColorCalibrationArray(), COLOR_CALIBRATION_MIN_INDEX, 0); + } + + /** + * @return The maximum value for all colors + */ + public int getDisplayColorCalibrationMax() { + if (isSupportedHIDL(FEATURE_DISPLAY_COLOR_CALIBRATION)) { + IDisplayColorCalibration displayColorCalibration = (IDisplayColorCalibration) + mHIDLMap.get(FEATURE_DISPLAY_COLOR_CALIBRATION); + try { + return displayColorCalibration.getMaxValue(); + } catch (RemoteException e) { + return 0; + } + } + + return getArrayValue(getDisplayColorCalibrationArray(), COLOR_CALIBRATION_MAX_INDEX, 0); + } + + /** + * Set the display color calibration to the given rgb triplet + * + * @param rgb RGB color calibration. Each value must be between + * {@link #getDisplayColorCalibrationMin()} and {@link #getDisplayColorCalibrationMax()}, + * inclusive. + * + * @return true on success, false otherwise. + */ + public boolean setDisplayColorCalibration(int[] rgb) { + try { + if (isSupportedHIDL(FEATURE_DISPLAY_COLOR_CALIBRATION)) { + IDisplayColorCalibration displayColorCalibration = (IDisplayColorCalibration) + mHIDLMap.get(FEATURE_DISPLAY_COLOR_CALIBRATION); + return displayColorCalibration.setCalibration( + new ArrayList(Arrays.asList(rgb[0], rgb[1], rgb[2]))); + } else if (checkService()) { + return sService.setDisplayColorCalibration(rgb); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * @return true if adaptive backlight should be enabled when sunlight enhancement + * is enabled. + */ + public boolean requireAdaptiveBacklightForSunlightEnhancement() { + if (isSupportedHIDL(FEATURE_SUNLIGHT_ENHANCEMENT)) { + return false; + } + + try { + if (checkService()) { + return sService.requireAdaptiveBacklightForSunlightEnhancement(); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * @return true if this implementation does it's own lux metering + */ + public boolean isSunlightEnhancementSelfManaged() { + if (isSupportedHIDL(FEATURE_SUNLIGHT_ENHANCEMENT)) { + return false; + } + + try { + if (checkService()) { + return sService.isSunlightEnhancementSelfManaged(); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * @return a list of available display modes on the devices + */ + public DisplayMode[] getDisplayModes() { + DisplayMode[] modes = null; + try { + if (isSupportedHIDL(FEATURE_DISPLAY_MODES)) { + IDisplayModes displayModes = (IDisplayModes) mHIDLMap.get(FEATURE_DISPLAY_MODES); + modes = HIDLHelper.fromHIDLModes(displayModes.getDisplayModes()); + } else if (checkService()) { + modes= sService.getDisplayModes(); + } + } catch (RemoteException e) { + } finally { + if (modes == null) { + return null; + } + final ArrayList remapped = new ArrayList(); + for (DisplayMode mode : modes) { + DisplayMode r = remapDisplayMode(mode); + if (r != null) { + remapped.add(r); + } + } + return remapped.toArray(new DisplayMode[0]); + } + } + + /** + * @return the currently active display mode + */ + public DisplayMode getCurrentDisplayMode() { + DisplayMode mode = null; + try { + if (isSupportedHIDL(FEATURE_DISPLAY_MODES)) { + IDisplayModes displayModes = (IDisplayModes) mHIDLMap.get(FEATURE_DISPLAY_MODES); + mode = HIDLHelper.fromHIDLMode(displayModes.getCurrentDisplayMode()); + } else if (checkService()) { + mode = sService.getCurrentDisplayMode(); + } + } catch (RemoteException e) { + } finally { + return mode != null ? remapDisplayMode(mode) : null; + } + } + + /** + * @return the default display mode to be set on boot + */ + public DisplayMode getDefaultDisplayMode() { + DisplayMode mode = null; + try { + if (isSupportedHIDL(FEATURE_DISPLAY_MODES)) { + IDisplayModes displayModes = (IDisplayModes) mHIDLMap.get(FEATURE_DISPLAY_MODES); + mode = HIDLHelper.fromHIDLMode(displayModes.getDefaultDisplayMode()); + } else if (checkService()) { + mode = sService.getDefaultDisplayMode(); + } + } catch (RemoteException e) { + } finally { + return mode != null ? remapDisplayMode(mode) : null; + } + } + + /** + * @return true if setting the mode was successful + */ + public boolean setDisplayMode(DisplayMode mode, boolean makeDefault) { + try { + if (isSupportedHIDL(FEATURE_DISPLAY_MODES)) { + IDisplayModes displayModes = (IDisplayModes) mHIDLMap.get(FEATURE_DISPLAY_MODES); + return displayModes.setDisplayMode(mode.id, makeDefault); + } else if (checkService()) { + return sService.setDisplayMode(mode, makeDefault); + } + } catch (RemoteException e) { + } + return false; + } + + private DisplayMode remapDisplayMode(DisplayMode in) { + if (in == null) { + return null; + } + if (mDisplayModeMappings.containsKey(in.name)) { + return new DisplayMode(in.id, mDisplayModeMappings.get(in.name)); + } + if (!mFilterDisplayModes) { + return in; + } + return null; + } + + /** + * @return the available range for color temperature adjustments + */ + public Range getColorBalanceRange() { + try { + if (isSupportedHIDL(FEATURE_COLOR_BALANCE)) { + IColorBalance colorBalance = (IColorBalance) mHIDLMap.get(FEATURE_COLOR_BALANCE); + return HIDLHelper.fromHIDLRange(colorBalance.getColorBalanceRange()); + } else if (checkService()) { + return new Range( + sService.getColorBalanceMin(), + sService.getColorBalanceMax()); + } + } catch (RemoteException e) { + } + return new Range(0, 0); + } + + /** + * @return the current color balance value + */ + public int getColorBalance() { + try { + if (isSupportedHIDL(FEATURE_COLOR_BALANCE)) { + IColorBalance colorBalance = (IColorBalance) mHIDLMap.get(FEATURE_COLOR_BALANCE); + return colorBalance.getColorBalance(); + } else if (checkService()) { + return sService.getColorBalance(); + } + } catch (RemoteException e) { + } + return 0; + } + + /** + * Sets the desired color balance. Must fall within the range obtained from + * getColorBalanceRange() + * + * @param value + * @return true if success + */ + public boolean setColorBalance(int value) { + try { + if (isSupportedHIDL(FEATURE_COLOR_BALANCE)) { + IColorBalance colorBalance = (IColorBalance) mHIDLMap.get(FEATURE_COLOR_BALANCE); + return colorBalance.setColorBalance(value); + } else if (checkService()) { + return sService.setColorBalance(value); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * Gets the current picture adjustment values + * + * @return HSIC object with current settings + */ + public HSIC getPictureAdjustment() { + try { + if (isSupportedHIDL(FEATURE_PICTURE_ADJUSTMENT)) { + IPictureAdjustment pictureAdjustment = (IPictureAdjustment) + mHIDLMap.get(FEATURE_PICTURE_ADJUSTMENT); + return HIDLHelper.fromHIDLHSIC(pictureAdjustment.getPictureAdjustment()); + } else if (checkService()) { + return sService.getPictureAdjustment(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * Gets the default picture adjustment for the current mode + * + * @return HSIC object with default settings + */ + public HSIC getDefaultPictureAdjustment() { + try { + if (isSupportedHIDL(FEATURE_PICTURE_ADJUSTMENT)) { + IPictureAdjustment pictureAdjustment = (IPictureAdjustment) + mHIDLMap.get(FEATURE_PICTURE_ADJUSTMENT); + return HIDLHelper.fromHIDLHSIC(pictureAdjustment.getDefaultPictureAdjustment()); + } else if (checkService()) { + return sService.getDefaultPictureAdjustment(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * Sets the desired hue/saturation/intensity/contrast + * + * @param hsic + * @return true if success + */ + public boolean setPictureAdjustment(final HSIC hsic) { + try { + if (isSupportedHIDL(FEATURE_PICTURE_ADJUSTMENT)) { + IPictureAdjustment pictureAdjustment = (IPictureAdjustment) + mHIDLMap.get(FEATURE_PICTURE_ADJUSTMENT); + return pictureAdjustment.setPictureAdjustment(HIDLHelper.toHIDLHSIC(hsic)); + } else if (checkService()) { + return sService.setPictureAdjustment(hsic); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * Get a list of ranges valid for picture adjustment. + * + * @return range list + */ + public List> getPictureAdjustmentRanges() { + try { + if (isSupportedHIDL(FEATURE_PICTURE_ADJUSTMENT)) { + IPictureAdjustment pictureAdjustment = (IPictureAdjustment) + mHIDLMap.get(FEATURE_PICTURE_ADJUSTMENT); + return Arrays.asList( + HIDLHelper.fromHIDLRange(pictureAdjustment.getHueRange()), + HIDLHelper.fromHIDLRange(pictureAdjustment.getSaturationRange()), + HIDLHelper.fromHIDLRange(pictureAdjustment.getIntensityRange()), + HIDLHelper.fromHIDLRange(pictureAdjustment.getContrastRange()), + HIDLHelper.fromHIDLRange(pictureAdjustment.getSaturationThresholdRange())); + } else if (checkService()) { + float[] ranges = sService.getPictureAdjustmentRanges(); + if (ranges.length > 7) { + return Arrays.asList(new Range(ranges[0], ranges[1]), + new Range(ranges[2], ranges[3]), + new Range(ranges[4], ranges[5]), + new Range(ranges[6], ranges[7]), + (ranges.length > 9 ? + new Range(ranges[8], ranges[9]) : + new Range(0.0f, 0.0f))); + } + } + } catch (RemoteException e) { + } + return null; + } + + /** + * @return true if service is valid + */ + private boolean checkService() { + if (sService == null) { + Log.w(TAG, "not connected to LineageHardwareManagerService"); + return false; + } + return true; + } + +} diff --git a/core/java/com/android/internal/lineage/hardware/LiveDisplayConfig.aidl b/core/java/com/android/internal/lineage/hardware/LiveDisplayConfig.aidl new file mode 100644 index 000000000000..5a9d951e7839 --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/LiveDisplayConfig.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2016 The CyanogenMod 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.lineage.hardware; + +parcelable LiveDisplayConfig; diff --git a/core/java/com/android/internal/lineage/hardware/LiveDisplayConfig.java b/core/java/com/android/internal/lineage/hardware/LiveDisplayConfig.java new file mode 100644 index 000000000000..56c17f18dcf8 --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/LiveDisplayConfig.java @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2016 The CyanogenMod 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.lineage.hardware; + +import static com.android.internal.lineage.hardware.LiveDisplayManager.FEATURE_COLOR_BALANCE; +import static com.android.internal.lineage.hardware.LiveDisplayManager.FEATURE_FIRST; +import static com.android.internal.lineage.hardware.LiveDisplayManager.FEATURE_LAST; +import static com.android.internal.lineage.hardware.LiveDisplayManager.MODE_FIRST; +import static com.android.internal.lineage.hardware.LiveDisplayManager.MODE_LAST; +import static com.android.internal.lineage.hardware.LiveDisplayManager.MODE_OFF; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Range; + +import java.util.Arrays; +import java.util.BitSet; +import java.util.List; + +import com.android.internal.lineage.util.Concierge; +import com.android.internal.lineage.util.Concierge.ParcelInfo; + +/** + * Holder class for LiveDisplay static configuration. + * + * This class holds various defaults and hardware capabilities + * which are involved with LiveDisplay. + */ +public class LiveDisplayConfig implements Parcelable { + + private final BitSet mCapabilities; + private final BitSet mAllModes = new BitSet(); + + private final int mDefaultDayTemperature; + private final int mDefaultNightTemperature; + private final int mDefaultMode; + + private final boolean mDefaultAutoContrast; + private final boolean mDefaultAutoOutdoorMode; + private final boolean mDefaultCABC; + private final boolean mDefaultColorEnhancement; + + private final Range mColorTemperatureRange; + private final Range mColorBalanceRange; + private final Range mHueRange; + private final Range mSaturationRange; + private final Range mIntensityRange; + private final Range mContrastRange; + private final Range mSaturationThresholdRange; + + public LiveDisplayConfig(BitSet capabilities, int defaultMode, + int defaultDayTemperature, int defaultNightTemperature, + boolean defaultAutoOutdoorMode, boolean defaultAutoContrast, + boolean defaultCABC, boolean defaultColorEnhancement, + Range colorTemperatureRange, + Range colorBalanceRange, + Range hueRange, + Range saturationRange, + Range intensityRange, + Range contrastRange, + Range saturationThresholdRange) { + super(); + mCapabilities = (BitSet) capabilities.clone(); + mAllModes.set(MODE_FIRST, MODE_LAST); + mDefaultMode = defaultMode; + mDefaultDayTemperature = defaultDayTemperature; + mDefaultNightTemperature = defaultNightTemperature; + mDefaultAutoContrast = defaultAutoContrast; + mDefaultAutoOutdoorMode = defaultAutoOutdoorMode; + mDefaultCABC = defaultCABC; + mDefaultColorEnhancement = defaultColorEnhancement; + mColorTemperatureRange = colorTemperatureRange; + mColorBalanceRange = colorBalanceRange; + mHueRange = hueRange; + mSaturationRange = saturationRange; + mIntensityRange = intensityRange; + mContrastRange = contrastRange; + mSaturationThresholdRange = saturationThresholdRange; + } + + private LiveDisplayConfig(Parcel parcel) { + // Read parcelable version via the Concierge + ParcelInfo parcelInfo = Concierge.receiveParcel(parcel); + int parcelableVersion = parcelInfo.getParcelVersion(); + + // temp vars + long capabilities = 0; + int defaultMode = 0; + int defaultDayTemperature = -1; + int defaultNightTemperature = -1; + boolean defaultAutoContrast = false; + boolean defaultAutoOutdoorMode = false; + boolean defaultCABC = false; + boolean defaultColorEnhancement = false; + int minColorTemperature = 0; + int maxColorTemperature = 0; + int minColorBalance = 0; + int maxColorBalance = 0; + float[] paRanges = new float[10]; + + capabilities = parcel.readLong(); + defaultMode = parcel.readInt(); + defaultDayTemperature = parcel.readInt(); + defaultNightTemperature = parcel.readInt(); + defaultAutoContrast = parcel.readInt() == 1; + defaultAutoOutdoorMode = parcel.readInt() == 1; + defaultCABC = parcel.readInt() == 1; + defaultColorEnhancement = parcel.readInt() == 1; + minColorTemperature = parcel.readInt(); + maxColorTemperature = parcel.readInt(); + minColorBalance = parcel.readInt(); + maxColorBalance = parcel.readInt(); + parcel.readFloatArray(paRanges); + + // set temps + mCapabilities = BitSet.valueOf(new long[] { capabilities }); + mAllModes.set(MODE_FIRST, MODE_LAST); + mDefaultMode = defaultMode; + mDefaultDayTemperature = defaultDayTemperature; + mDefaultNightTemperature = defaultNightTemperature; + mDefaultAutoContrast = defaultAutoContrast; + mDefaultAutoOutdoorMode = defaultAutoOutdoorMode; + mDefaultCABC = defaultCABC; + mDefaultColorEnhancement = defaultColorEnhancement; + mColorTemperatureRange = Range.create(minColorTemperature, maxColorTemperature); + mColorBalanceRange = Range.create(minColorBalance, maxColorBalance); + mHueRange = Range.create(paRanges[0], paRanges[1]); + mSaturationRange = Range.create(paRanges[2], paRanges[3]); + mIntensityRange = Range.create(paRanges[4], paRanges[5]); + mContrastRange = Range.create(paRanges[6], paRanges[7]); + mSaturationThresholdRange = Range.create(paRanges[8], paRanges[9]); + + // Complete parcel info for the concierge + parcelInfo.complete(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("capabilities=").append(mCapabilities.toString()); + sb.append(" defaultMode=").append(mDefaultMode); + sb.append(" defaultDayTemperature=").append(mDefaultDayTemperature); + sb.append(" defaultNightTemperature=").append(mDefaultNightTemperature); + sb.append(" defaultAutoOutdoorMode=").append(mDefaultAutoOutdoorMode); + sb.append(" defaultAutoContrast=").append(mDefaultAutoContrast); + sb.append(" defaultCABC=").append(mDefaultCABC); + sb.append(" defaultColorEnhancement=").append(mDefaultColorEnhancement); + sb.append(" colorTemperatureRange=").append(mColorTemperatureRange); + if (mCapabilities.get(LiveDisplayManager.FEATURE_COLOR_BALANCE)) { + sb.append(" colorBalanceRange=").append(mColorBalanceRange); + } + if (mCapabilities.get(LiveDisplayManager.FEATURE_PICTURE_ADJUSTMENT)) { + sb.append(" hueRange=").append(mHueRange); + sb.append(" saturationRange=").append(mSaturationRange); + sb.append(" intensityRange=").append(mIntensityRange); + sb.append(" contrastRange=").append(mContrastRange); + sb.append(" saturationThresholdRange=").append(mSaturationThresholdRange); + } + return sb.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + // Tell the concierge to prepare the parcel + ParcelInfo parcelInfo = Concierge.prepareParcel(out); + + // ==== FIG ===== + long[] caps = mCapabilities.toLongArray(); + out.writeLong(caps != null && caps.length > 0 ? caps[0] : 0L); + out.writeInt(mDefaultMode); + out.writeInt(mDefaultDayTemperature); + out.writeInt(mDefaultNightTemperature); + out.writeInt(mDefaultAutoContrast ? 1 : 0); + out.writeInt(mDefaultAutoOutdoorMode ? 1 : 0); + out.writeInt(mDefaultCABC ? 1 : 0); + out.writeInt(mDefaultColorEnhancement ? 1 : 0); + out.writeInt(mColorTemperatureRange.getLower()); + out.writeInt(mColorTemperatureRange.getUpper()); + out.writeInt(mColorBalanceRange.getLower()); + out.writeInt(mColorBalanceRange.getUpper()); + out.writeFloatArray(new float[] { + mHueRange.getLower(), mHueRange.getUpper(), + mSaturationRange.getLower(), mSaturationRange.getUpper(), + mIntensityRange.getLower(), mIntensityRange.getUpper(), + mContrastRange.getLower(), mContrastRange.getUpper(), + mSaturationThresholdRange.getLower(), mSaturationThresholdRange.getUpper() } ); + + // Complete the parcel info for the concierge + parcelInfo.complete(); + } + + /** + * Checks if a particular feature or mode is supported by the system. + * + * @param feature + * @return true if capable + */ + public boolean hasFeature(int feature) { + return ((feature >= MODE_FIRST && feature <= MODE_LAST) || + (feature >= FEATURE_FIRST && feature <= FEATURE_LAST)) && + (feature == MODE_OFF || mCapabilities.get(feature)); + } + + /** + * Checks if LiveDisplay is available for use on this device. + * + * @return true if any feature is enabled + */ + public boolean isAvailable() { + return !mCapabilities.isEmpty(); + } + + /** + * Checks if LiveDisplay has support for adaptive modes. + * + * @return true if adaptive modes are available + */ + public boolean hasModeSupport() { + return isAvailable() && mCapabilities.intersects(mAllModes); + } + + /** + * Gets the default color temperature to use in the daytime. This is typically + * set to 6500K, however this may not be entirely accurate. Use this value for + * resetting controls to the default. + * + * @return the default day temperature in K + */ + public int getDefaultDayTemperature() { + return mDefaultDayTemperature; + } + + /** + * Gets the default color temperature to use at night. This is typically set + * to 4500K, but this may not be entirely accurate. Use this value for resetting + * controls to defaults. + * + * @return the default night color temperature + */ + public int getDefaultNightTemperature() { + return mDefaultNightTemperature; + } + + /** + * Get the default adaptive mode. + * + * @return the default mode + */ + public int getDefaultMode() { + return mDefaultMode; + } + + /** + * Get the default value for auto contrast + * + * @return true if enabled + */ + public boolean getDefaultAutoContrast() { + return mDefaultAutoContrast; + } + + /** + * Get the default value for automatic outdoor mode + * + * @return true if enabled + */ + public boolean getDefaultAutoOutdoorMode() { + return mDefaultAutoOutdoorMode; + } + + /** + * Get the default value for CABC + * + * @return true if enabled + */ + public boolean getDefaultCABC() { + return mDefaultCABC; + } + + /** + * Get the default value for color enhancement + * + * @return true if enabled + */ + public boolean getDefaultColorEnhancement() { + return mDefaultColorEnhancement; + } + + /** + * Get the range of supported color temperatures + * + * @return range in Kelvin + */ + public Range getColorTemperatureRange() { + return mColorTemperatureRange; + } + + /** + * Get the range of supported color balance + * + * @return linear range which maps into the temperature range curve + */ + public Range getColorBalanceRange() { + return mColorBalanceRange; + } + + /** + * Get the supported range for hue adjustment + * + * @return float range + */ + public Range getHueRange() { return mHueRange; } + + /** + * Get the supported range for saturation adjustment + * + * @return float range + */ + public Range getSaturationRange() { return mSaturationRange; } + + /** + * Get the supported range for intensity adjustment + * + * @return float range + */ + public Range getIntensityRange() { return mIntensityRange; } + + /** + * Get the supported range for contrast adjustment + * + * @return float range + */ + public Range getContrastRange() { return mContrastRange; } + + /** + * Get the supported range for saturation threshold adjustment + * + * @return float range + */ + public Range getSaturationThresholdRange() { + return mSaturationThresholdRange; + } + + + /** + * Convenience method to get a list of all picture adjustment ranges + * with a single call. + * + * @return List of float ranges + */ + public List> getPictureAdjustmentRanges() { + return Arrays.asList(mHueRange, mSaturationRange, mIntensityRange, + mContrastRange, mSaturationThresholdRange); + } + + /** @hide */ + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public LiveDisplayConfig createFromParcel(Parcel in) { + return new LiveDisplayConfig(in); + } + + @Override + public LiveDisplayConfig[] newArray(int size) { + return new LiveDisplayConfig[size]; + } + }; +} diff --git a/core/java/com/android/internal/lineage/hardware/LiveDisplayManager.java b/core/java/com/android/internal/lineage/hardware/LiveDisplayManager.java new file mode 100644 index 000000000000..a97c8a95fa8d --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/LiveDisplayManager.java @@ -0,0 +1,535 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * 2018-2021 The LineageOS 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.lineage.hardware; + +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; +import android.util.Range; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.android.internal.lineage.app.LineageContextConstants; + +/** + * LiveDisplay is an advanced set of features for improving + * display quality under various ambient conditions. + * + * The backend service is constructed with a set of LiveDisplayFeatures + * which provide capabilities such as outdoor mode, night mode, + * and calibration. It interacts with LineageHardwareService to relay + * changes down to the lower layers. + * + * Multiple adaptive modes are supported, and various hardware + * features such as CABC, ACO and color enhancement are also + * managed by LiveDisplay. + */ +public class LiveDisplayManager { + + /** + * Disable all LiveDisplay adaptive features + */ + public static final int MODE_OFF = 0; + + /** + * Change color temperature to night mode + */ + public static final int MODE_NIGHT = 1; + + /** + * Enable automatic detection of appropriate mode + */ + public static final int MODE_AUTO = 2; + + /** + * Increase brightness/contrast/saturation for sunlight + */ + public static final int MODE_OUTDOOR = 3; + + /** + * Change color temperature to day mode, and allow + * detection of outdoor conditions + */ + public static final int MODE_DAY = 4; + + /** @hide */ + public static final int MODE_FIRST = MODE_OFF; + /** @hide */ + public static final int MODE_LAST = MODE_DAY; + + /** + * Content adaptive backlight control, adjust images to + * increase brightness in order to reduce backlight level + */ + public static final int FEATURE_CABC = 10; + + /** + * Adjust images to increase contrast + */ + public static final int FEATURE_AUTO_CONTRAST = 11; + + /** + * Adjust image to improve saturation and color + */ + public static final int FEATURE_COLOR_ENHANCEMENT = 12; + + /** + * Capable of adjusting RGB levels + */ + public static final int FEATURE_COLOR_ADJUSTMENT = 13; + + /** + * System supports outdoor mode, but environmental sensing + * is done by an external application. + */ + public static final int FEATURE_MANAGED_OUTDOOR_MODE = 14; + + /** + * System supports multiple display calibrations + * for different viewing intents. + */ + public static final int FEATURE_DISPLAY_MODES = 15; + + /** + * System supports direct range-based control of display + * color balance (temperature). This is preferred over + * simple RGB adjustment. + */ + public static final int FEATURE_COLOR_BALANCE = 16; + + /** + * System supports manual hue/saturation/intensity/contrast + * adjustment of display. + */ + public static final int FEATURE_PICTURE_ADJUSTMENT = 17; + + /** + * System supports grayscale matrix overlay + */ + public static final int FEATURE_READING_ENHANCEMENT = 18; + + /** + * System supports anti flicker mode + */ + public static final int FEATURE_ANTI_FLICKER = 19; + + public static final int ADJUSTMENT_HUE = 0; + public static final int ADJUSTMENT_SATURATION = 1; + public static final int ADJUSTMENT_INTENSITY = 2; + public static final int ADJUSTMENT_CONTRAST = 3; + + /** @hide */ + public static final int FEATURE_FIRST = FEATURE_CABC; + /** @hide */ + public static final int FEATURE_LAST = FEATURE_ANTI_FLICKER; + + private static final String TAG = "LiveDisplay"; + + private final Context mContext; + private LiveDisplayConfig mConfig; + + private static LiveDisplayManager sInstance; + private static ILiveDisplayService sService; + + /** + * @hide to prevent subclassing from outside of the framework + */ + private LiveDisplayManager(Context context) { + Context appContext = context.getApplicationContext(); + if (appContext != null) { + mContext = appContext; + } else { + mContext = context; + } + sService = getService(); + + if (!checkService()) { + Log.wtf(TAG, "Unable to get LiveDisplayService. The service either" + + " crashed, was not started, or the interface has been called to early in" + + " SystemServer init"); + } + } + + /** + * Get or create an instance of the {@link com.android.internal.lineage.hardware.LiveDisplayManager} + * @param context + * @return {@link LiveDisplayManager} + */ + public synchronized static LiveDisplayManager getInstance(Context context) { + if (sInstance == null) { + sInstance = new LiveDisplayManager(context); + } + return sInstance; + } + + /** @hide */ + public static ILiveDisplayService getService() { + if (sService != null) { + return sService; + } + IBinder b = ServiceManager.getService(LineageContextConstants.LINEAGE_LIVEDISPLAY_SERVICE); + if (b != null) { + sService = ILiveDisplayService.Stub.asInterface(b); + return sService; + } + return null; + } + + /** + * @return true if service is valid + */ + private boolean checkService() { + if (sService == null) { + Log.w(TAG, "not connected to LineageHardwareManagerService"); + return false; + } + return true; + } + + /** + * Gets the static configuration and settings. + * + * @return the configuration + */ + public LiveDisplayConfig getConfig() { + try { + if (mConfig == null) { + mConfig = checkService() ? sService.getConfig() : null; + } + return mConfig; + } catch (RemoteException e) { + return null; + } + } + + /** + * Returns the current adaptive mode. + * + * @return id of the selected mode + */ + public int getMode() { + try { + return checkService() ? sService.getMode() : MODE_OFF; + } catch (RemoteException e) { + return MODE_OFF; + } + } + + /** + * Selects a new adaptive mode. + * + * @param mode + * @return true if the mode was selected + */ + public boolean setMode(int mode) { + try { + return checkService() && sService.setMode(mode); + } catch (RemoteException e) { + return false; + } + } + + /** + * Checks if the auto contrast optimization feature is enabled. + * + * @return true if enabled + */ + public boolean isAutoContrastEnabled() { + try { + return checkService() && sService.isAutoContrastEnabled(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Sets the state of auto contrast optimization + * + * @param enabled + * @return true if state was changed + */ + public boolean setAutoContrastEnabled(boolean enabled) { + try { + return checkService() && sService.setAutoContrastEnabled(enabled); + } catch (RemoteException e) { + return false; + } + } + + /** + * Checks if the CABC feature is enabled + * + * @return true if enabled + */ + public boolean isCABCEnabled() { + try { + return checkService() && sService.isCABCEnabled(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Sets the state of CABC + * + * @param enabled + * @return true if state was changed + */ + public boolean setCABCEnabled(boolean enabled) { + try { + return checkService() && sService.setCABCEnabled(enabled); + } catch (RemoteException e) { + return false; + } + } + + /** + * Checks if the color enhancement feature is enabled + * + * @return true if enabled + */ + public boolean isColorEnhancementEnabled() { + try { + return checkService() && sService.isColorEnhancementEnabled(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Sets the state of color enhancement + * + * @param enabled + * @return true if state was changed + */ + public boolean setColorEnhancementEnabled(boolean enabled) { + try { + return checkService() && sService.setColorEnhancementEnabled(enabled); + } catch (RemoteException e) { + return false; + } + } + + /** + * Gets the user-specified color temperature to use in the daytime. + * + * @return the day color temperature + */ + public int getDayColorTemperature() { + try { + return checkService() ? sService.getDayColorTemperature() : -1; + } catch (RemoteException e) { + return -1; + } + } + + /** + * Sets the color temperature to use in the daytime. + * + * @param temperature + * @return true if state was changed + */ + public boolean setDayColorTemperature(int temperature) { + try { + return checkService() && sService.setDayColorTemperature(temperature); + } catch (RemoteException e) { + return false; + } + } + + /** + * Gets the user-specified color temperature to use at night. + * + * @return the night color temperature + */ + public int getNightColorTemperature() { + try { + return checkService() ? sService.getNightColorTemperature() : -1; + } catch (RemoteException e) { + return -1; + } + } + + /** + * Sets the color temperature to use at night. + * + * @param temperature + * @return true if state was changed + */ + public boolean setNightColorTemperature(int temperature) { + try { + return checkService() && sService.setNightColorTemperature(temperature); + } catch (RemoteException e) { + return false; + } + } + + /** + * Checks if outdoor mode should be enabled automatically when under extremely high + * ambient light. This is typically around 12000 lux. + * + * @return if outdoor conditions should be detected + */ + public boolean isAutomaticOutdoorModeEnabled() { + try { + return checkService() && sService.isAutomaticOutdoorModeEnabled(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Enables automatic detection of outdoor conditions. Outdoor mode is triggered + * when high ambient light is detected and it's not night. + * + * @param enabled + * @return true if state was changed + */ + public boolean setAutomaticOutdoorModeEnabled(boolean enabled) { + try { + return checkService() && sService.setAutomaticOutdoorModeEnabled(enabled); + } catch (RemoteException e) { + return false; + } + } + + /** + * Gets the current RGB triplet which is applied as a color adjustment. + * The values are floats between 0 and 1. A setting of { 1.0, 1.0, 1.0 } + * means that no adjustment is made. + * + * @return array of { R, G, B } offsets + */ + public float[] getColorAdjustment() { + try { + if (checkService()) { + return sService.getColorAdjustment(); + } + } catch (RemoteException e) { + } + return new float[] { 1.0f, 1.0f, 1.0f }; + } + + /** + * Sets the color adjustment to use. This can be set by the user to calibrate + * their screen. This should be sent in the format { R, G, B } as floats from + * 0 to 1. A setting of { 1.0, 1.0, 1.0 } means that no adjustment is made. + * The hardware implementation may refuse certain values which make the display + * unreadable, such as { 0, 0, 0 }. This calibration will be combined with other + * internal adjustments, such as night mode, if necessary. + * + * @param array of { R, G, B } offsets + * @return true if state was changed + */ + public boolean setColorAdjustment(float[] adj) { + try { + return checkService() && sService.setColorAdjustment(adj); + } catch (RemoteException e) { + return false; + } + } + + /** + * Gets the current picture adjustment settings (hue, saturation, intensity, contrast) + * + * @return HSIC object with current settings + */ + public HSIC getPictureAdjustment() { + try { + if (checkService()) { + return sService.getPictureAdjustment(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * Sets a new picture adjustment + * + * @param hsic + * @return true if success + */ + public boolean setPictureAdjustment(final HSIC hsic) { + try { + return checkService() && sService.setPictureAdjustment(hsic); + } catch (RemoteException e) { + } + return false; + } + + /** + * Gets the default picture adjustment for the current display mode + * + * @return HSIC object with default values + */ + public HSIC getDefaultPictureAdjustment() { + try { + if (checkService()) { + return sService.getDefaultPictureAdjustment(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * Determine whether night mode is enabled (be it automatic or manual) + */ + public boolean isNightModeEnabled() { + // This method might be called before config has been set up + // so a NPE would have been thrown, just report night mode is disabled instead + try { + return getMode() == MODE_NIGHT || sService.isNight(); + } catch (NullPointerException e) { + Log.w(TAG, "Can\'t check whether night mode is enabled because the service isn\'t ready"); + } catch (RemoteException ignored) { + } + return false; + } + + /** + * Checks if the anti flicker feature is enabled + * + * @return true if enabled + */ + public boolean isAntiFlickerEnabled() { + try { + return checkService() && sService.isAntiFlickerEnabled(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Sets the state of anti flicker + * + * @param enabled + * @return true if state was changed + */ + public boolean setAntiFlickerEnabled(boolean enabled) { + try { + return checkService() && sService.setAntiFlickerEnabled(enabled); + } catch (RemoteException e) { + return false; + } + } +} diff --git a/core/java/com/android/internal/lineage/hardware/PictureAdjustment.java b/core/java/com/android/internal/lineage/hardware/PictureAdjustment.java new file mode 100644 index 000000000000..c8ce51d22d07 --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/PictureAdjustment.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * Copyright (C) 2018 The LineageOS 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.lineage.hardware; + +import android.util.Range; + +import com.android.internal.lineage.hardware.HSIC; + +/** + * Picture adjustment support + * + * Allows tuning of hue, saturation, intensity, and contrast levels + * of the display + */ +public class PictureAdjustment { + + /** + * Whether device supports picture adjustment + * + * @return boolean Supported devices must return always true + */ + public static boolean isSupported() { + return false; + } + + /** + * This method returns the current picture adjustment values based + * on the selected DisplayMode. + * + * @return the HSIC object or null if not supported + */ + public static HSIC getHSIC() { + return null; + } + + /** + * This method returns the default picture adjustment values. + * + * If DisplayModes are available, this may change depending on the + * selected mode. + * + * @return the HSIC object or null if not supported + */ + public static HSIC getDefaultHSIC() { + return null; + } + + /** + * This method allows to set the picture adjustment + * + * @param hsic + * @return boolean Must be false if feature is not supported or the operation + * failed; true in any other case. + */ + public static boolean setHSIC(final HSIC hsic) { + return false; + } + + /** + * Get the range available for hue adjustment + * @return range of floats + */ + public static Range getHueRange() { + return new Range(0.0f, 0.0f); + } + + /** + * Get the range available for saturation adjustment + * @return range of floats + */ + public static Range getSaturationRange() { + return new Range(0.0f, 0.0f); + } + + /** + * Get the range available for intensity adjustment + * @return range of floats + */ + public static Range getIntensityRange() { + return new Range(0.0f, 0.0f); + } + + /** + * Get the range available for contrast adjustment + * @return range of floats + */ + public static Range getContrastRange() { + return new Range(0.0f, 0.0f); + } + + /** + * Get the range available for saturation threshold adjustment + * + * This is the threshold where the display becomes fully saturated + * + * @return range of floats + */ + public static Range getSaturationThresholdRange() { + return new Range(0.0f, 0.0f); + } +} diff --git a/core/java/com/android/internal/lineage/hardware/ReadingEnhancement.java b/core/java/com/android/internal/lineage/hardware/ReadingEnhancement.java new file mode 100644 index 000000000000..8f8adefcb9de --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/ReadingEnhancement.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018 The LineageOS 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.lineage.hardware; + +import com.android.internal.util.custom.FileUtils; + +/** + * Reader mode + */ +public class ReadingEnhancement { + + private static final String TAG = "ReadingEnhancement"; + + private static final String FILE_READING = "/sys/class/graphics/fb0/reading_mode"; + + /** + * Whether device supports Reader Mode + * + * @return boolean Supported devices must return always true + */ + public static boolean isSupported() { + return FileUtils.isFileReadable(FILE_READING) && FileUtils.isFileWritable(FILE_READING); + } + + /** + * This method return the current activation status of Reader Mode + * + * @return boolean Must be false when Reader Mode is not supported or not activated, + * or the operation failed while reading the status; true in any other case. + */ + public static boolean isEnabled() { + return Integer.parseInt(FileUtils.readOneLine(FILE_READING)) > 0; + } + + /** + * This method allows to setup Reader Mode + * + * @param status The new Reader Mode status + * @return boolean Must be false if Reader Mode is not supported or the operation + * failed; true in any other case. + */ + public static boolean setEnabled(boolean status) { + return FileUtils.writeLine(FILE_READING, status ? "1" : "0"); + } + +} diff --git a/core/java/com/android/internal/lineage/hardware/SunlightEnhancement.java b/core/java/com/android/internal/lineage/hardware/SunlightEnhancement.java new file mode 100644 index 000000000000..70bc08f922bb --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/SunlightEnhancement.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * Copyright (C) 2018 The LineageOS 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.lineage.hardware; + +import android.util.Log; + +import com.android.internal.util.custom.FileUtils; + +/** + * Facemelt mode! + */ +public class SunlightEnhancement { + + private static final String TAG = "SunlightEnhancement"; + + private static final String FACEMELT_PATH = getFacemeltPath(); + private static final String FACEMELT_MODE = getFacemeltMode(); + + private static final String FILE_HBM = "/sys/class/graphics/fb0/hbm"; + private static final String FILE_SRE = "/sys/class/graphics/fb0/sre"; + + private static String getFacemeltPath() { + if (FileUtils.fileExists(FILE_HBM)) { + return FILE_HBM; + } else { + return FILE_SRE; + } + } + + private static String getFacemeltMode() { + if (FileUtils.fileExists(FILE_HBM)) { + return "1"; + } else { + return "2"; + } + } + + /** + * Whether device supports sunlight enhancement + * + * @return boolean Supported devices must return always true + */ + public static boolean isSupported() { + return FileUtils.isFileReadable(FACEMELT_PATH) && FileUtils.isFileWritable(FACEMELT_PATH); + } + + /** + * This method return the current activation status of sunlight enhancement + * + * @return boolean Must be false when sunlight enhancement is not supported or not activated, + * or the operation failed while reading the status; true in any other case. + */ + public static boolean isEnabled() { + return Integer.parseInt(FileUtils.readOneLine(FACEMELT_PATH)) > 0; + } + + /** + * This method allows to setup sunlight enhancement + * + * @param status The new sunlight enhancement status + * @return boolean Must be false if sunlight enhancement is not supported or the operation + * failed; true in any other case. + */ + public static boolean setEnabled(boolean status) { + return FileUtils.writeLine(FACEMELT_PATH, status ? FACEMELT_MODE : "0"); + } + + /** + * Whether adaptive backlight (CABL / CABC) is required to be enabled + * + * @return boolean False if adaptive backlight is not a dependency + */ + public static boolean isAdaptiveBacklightRequired() { + return false; + } + + /** + * Set this to true if the implementation is self-managed and does + * it's own ambient sensing. In this case, setEnabled is assumed + * to toggle the feature on or off, but not activate it. If set + * to false, LiveDisplay will call setEnabled when the ambient lux + * threshold is crossed. + * + * @return true if this enhancement is self-managed + */ + public static boolean isSelfManaged() { + return false; + } +} diff --git a/core/java/com/android/internal/lineage/hardware/TouchscreenGesture.aidl b/core/java/com/android/internal/lineage/hardware/TouchscreenGesture.aidl new file mode 100644 index 000000000000..060686bcdc5d --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/TouchscreenGesture.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * 2017 The LineageOS 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.lineage.hardware; + +parcelable TouchscreenGesture; diff --git a/core/java/com/android/internal/lineage/hardware/TouchscreenGesture.java b/core/java/com/android/internal/lineage/hardware/TouchscreenGesture.java new file mode 100644 index 000000000000..e7e6a3b682e3 --- /dev/null +++ b/core/java/com/android/internal/lineage/hardware/TouchscreenGesture.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * 2017 The LineageOS 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.lineage.hardware; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Touchscreen gestures API + * + * A device may implement several touchscreen gestures for use while + * the display is turned off, such as drawing alphabets and shapes. + * These gestures can be interpreted by userspace to activate certain + * actions and launch certain apps, such as to skip music tracks, + * to turn on the flashlight, or to launch the camera app. + * + * This *should always* be supported by the hardware directly. + * A lot of recent touch controllers have a firmware option for this. + * + * This API provides support for enumerating the gestures + * supported by the touchscreen. + * + * A TouchscreenGesture is referenced by it's identifier and carries an + * associated name (up to the user to translate this value). + */ +public class TouchscreenGesture implements Parcelable { + + public final int id; + public final String name; + public final int keycode; + + public TouchscreenGesture(int id, String name, int keycode) { + this.id = id; + this.name = name; + this.keycode = keycode; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(id); + parcel.writeString(name); + parcel.writeInt(keycode); + } + + /** @hide */ + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + public TouchscreenGesture createFromParcel(Parcel in) { + return new TouchscreenGesture(in.readInt(), in.readString(), in.readInt()); + } + + @Override + public TouchscreenGesture[] newArray(int size) { + return new TouchscreenGesture[size]; + } + }; +} diff --git a/core/java/com/android/internal/lineage/health/HealthInterface.java b/core/java/com/android/internal/lineage/health/HealthInterface.java new file mode 100644 index 000000000000..5fc7f1c021a2 --- /dev/null +++ b/core/java/com/android/internal/lineage/health/HealthInterface.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2023 The LineageOS 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.lineage.health; + +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import com.android.internal.lineage.app.LineageContextConstants; + +public class HealthInterface { + /** + * No config set. This value is invalid and does not have any effects + */ + public static final int MODE_NONE = 0; + + /** + * Automatic config + */ + public static final int MODE_AUTO = 1; + + /** + * Manual config mode + */ + public static final int MODE_MANUAL = 2; + + /** + * Limit config mode + */ + public static final int MODE_LIMIT = 3; + + private static final String TAG = "HealthInterface"; + private static IHealthInterface sService; + private static HealthInterface sInstance; + private Context mContext; + private HealthInterface(Context context) { + Context appContext = context.getApplicationContext(); + mContext = appContext == null ? context : appContext; + sService = getService(); + } + /** + * Get or create an instance of the {@link lineageos.health.HealthInterface} + * + * @param context Used to get the service + * @return {@link HealthInterface} + */ + public static synchronized HealthInterface getInstance(Context context) { + if (sInstance == null) { + sInstance = new HealthInterface(context); + } + return sInstance; + } + /** @hide **/ + public static IHealthInterface getService() { + if (sService != null) { + return sService; + } + IBinder b = ServiceManager.getService(LineageContextConstants.LINEAGE_HEALTH_INTERFACE); + sService = IHealthInterface.Stub.asInterface(b); + if (sService == null) { + Log.e(TAG, "null health service, SAD!"); + return null; + } + return sService; + } + + /** + * @return true if service is valid + */ + private boolean checkService() { + if (sService == null) { + Log.w(TAG, "not connected to LineageHardwareManagerService"); + return false; + } + return true; + } + + /** + * Returns whether charging control is supported + * + * @return true if charging control is supported + */ + public boolean isChargingControlSupported() { + try { + return checkService() && sService.isChargingControlSupported(); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + + return false; + } + + /** + * Returns whether charging control is supported + * + * @return true if charging control is supported + */ + public static boolean isChargingControlSupported(Context context) { + try { + return getInstance(context).isChargingControlSupported(); + } catch (RuntimeException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + return false; + } + + /** + * Returns the charging control enabled status + * + * @return whether charging control has been enabled + */ + public boolean getEnabled() { + try { + return checkService() && sService.getChargingControlEnabled(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Set charging control enable status + * + * @param enabled whether charging control should be enabled + * @return true if the enabled status was successfully set + */ + public boolean setEnabled(boolean enabled) { + try { + return checkService() && sService.setChargingControlEnabled(enabled); + } catch (RemoteException e) { + return false; + } + } + + /** + * Returns the current charging control mode + * + * @return id of the charging control mode + */ + public int getMode() { + try { + return checkService() ? sService.getChargingControlMode() : MODE_NONE; + } catch (RemoteException e) { + return MODE_NONE; + } + } + + /** + * Selects the new charging control mode + * + * @param mode the new charging control mode + * @return true if the mode was successfully set + */ + public boolean setMode(int mode) { + try { + return checkService() && sService.setChargingControlMode(mode); + } catch (RemoteException e) { + return false; + } + } + + /** + * Gets the charging control start time + * + * @return the seconds of the day of the start time + */ + public int getStartTime() { + try { + return checkService() ? sService.getChargingControlStartTime() : 0; + } catch (RemoteException e) { + return 0; + } + } + + /** + * Sets the charging control start time + * + * @param time the seconds of the day of the start time + * @return true if the start time was successfully set + */ + public boolean setStartTime(int time) { + try { + return checkService() && sService.setChargingControlStartTime(time); + } catch (RemoteException e) { + return false; + } + } + + /** + * Gets the charging control target time + * + * @return the seconds of the day of the target time + */ + public int getTargetTime() { + try { + return checkService() ? sService.getChargingControlTargetTime() : 0; + } catch (RemoteException e) { + return 0; + } + } + + /** + * Sets the charging control target time + * + * @param time the seconds of the day of the target time + * @return true if the target time was successfully set + */ + public boolean setTargetTime(int time) { + try { + return checkService() && sService.setChargingControlTargetTime(time); + } catch (RemoteException e) { + return false; + } + } + + /** + * Gets the charging control limit + * + * @return the charging control limit + */ + public int getLimit() { + try { + return checkService() ? sService.getChargingControlLimit() : 100; + } catch (RemoteException e) { + return 0; + } + } + + /** + * Sets the charging control limit + * + * @param limit the charging control limit + * @return true if the limit was successfully set + */ + public boolean setLimit(int limit) { + try { + return checkService() && sService.setChargingControlLimit(limit); + } catch (RemoteException e) { + return false; + } + } + + /** + * Resets the charging control setting to default + * + * @return true if the setting was successfully reset + */ + public boolean reset() { + try { + return checkService() && sService.resetChargingControl(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Returns whether the device's battery control bypasses battery + * + * @return true if the charging control bypasses battery + */ + public boolean allowFineGrainedSettings() { + try { + return checkService() && sService.allowFineGrainedSettings(); + } catch (RemoteException e) { + return false; + } + } +} diff --git a/core/java/com/android/internal/lineage/health/IHealthInterface.aidl b/core/java/com/android/internal/lineage/health/IHealthInterface.aidl new file mode 100644 index 000000000000..c2e9759ce6f1 --- /dev/null +++ b/core/java/com/android/internal/lineage/health/IHealthInterface.aidl @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2023 The LineageOS 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.lineage.health; + +/** @hide */ +interface IHealthInterface { + boolean isChargingControlSupported(); + + boolean getChargingControlEnabled(); + boolean setChargingControlEnabled(boolean enabled); + + int getChargingControlMode(); + boolean setChargingControlMode(int mode); + + int getChargingControlStartTime(); + boolean setChargingControlStartTime(int time); + + int getChargingControlTargetTime(); + boolean setChargingControlTargetTime(int time); + + int getChargingControlLimit(); + boolean setChargingControlLimit(int limit); + + boolean resetChargingControl(); + boolean allowFineGrainedSettings(); +} diff --git a/core/java/com/android/internal/lineage/util/ColorUtils.java b/core/java/com/android/internal/lineage/util/ColorUtils.java new file mode 100644 index 000000000000..4d3c9059c903 --- /dev/null +++ b/core/java/com/android/internal/lineage/util/ColorUtils.java @@ -0,0 +1,546 @@ +/* + * Copyright (c) 2011-2016 The CyanogenMod 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.lineage.util; + +import android.app.ActivityThread; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.MathUtils; + +import com.android.internal.lineage.util.palette.Palette; + +import java.util.Collections; +import java.util.Comparator; + +/** + * Helper class for colorspace conversions, and color-related + * algorithms which may be generally useful. + */ +public class ColorUtils { + + private static int[] SOLID_COLORS = new int[] { + Color.RED, 0xFFFFA500, Color.YELLOW, Color.GREEN, Color.CYAN, + Color.BLUE, Color.MAGENTA, Color.WHITE, Color.BLACK + }; + + private static float[] COEFFICIENTS = new float[9]; + + static { + String[] coefficients = ActivityThread.currentApplication().getApplicationContext() + .getResources().getStringArray( + com.android.internal.R.array.config_nightDisplayColorTemperatureCoefficients); + for (int i = 0; i < 9 && i < coefficients.length; i++) { + COEFFICIENTS[i] = Float.valueOf(coefficients[i]); + } + } + + /** + * Drop the alpha component from an RGBA packed int and return + * a non sign-extended RGB int. + * + * @param rgba + * @return rgb + */ + public static int dropAlpha(int rgba) { + return rgba & 0x00FFFFFF; + } + + /** + * Converts an RGB packed int into L*a*b space, which is well-suited for finding + * perceptual differences in color + * + * @param rgb A 32-bit value of packed RGB ints + * @return array of Lab values of size 3 + */ + public static float[] convertRGBtoLAB(int rgb) { + float[] lab = new float[3]; + float fx, fy, fz; + float eps = 216.f / 24389.f; + float k = 24389.f / 27.f; + + float Xr = 0.964221f; // reference white D50 + float Yr = 1.0f; + float Zr = 0.825211f; + + // RGB to XYZ + float r = Color.red(rgb) / 255.f; //R 0..1 + float g = Color.green(rgb) / 255.f; //G 0..1 + float b = Color.blue(rgb) / 255.f; //B 0..1 + + // assuming sRGB (D65) + if (r <= 0.04045) + r = r / 12; + else + r = (float) Math.pow((r + 0.055) / 1.055, 2.4); + + if (g <= 0.04045) + g = g / 12; + else + g = (float) Math.pow((g + 0.055) / 1.055, 2.4); + + if (b <= 0.04045) + b = b / 12; + else + b = (float) Math.pow((b + 0.055) / 1.055, 2.4); + + float X = 0.436052025f * r + 0.385081593f * g + 0.143087414f * b; + float Y = 0.222491598f * r + 0.71688606f * g + 0.060621486f * b; + float Z = 0.013929122f * r + 0.097097002f * g + 0.71418547f * b; + + // XYZ to Lab + float xr = X / Xr; + float yr = Y / Yr; + float zr = Z / Zr; + + if (xr > eps) + fx = (float) Math.pow(xr, 1 / 3.); + else + fx = (float) ((k * xr + 16.) / 116.); + + if (yr > eps) + fy = (float) Math.pow(yr, 1 / 3.); + else + fy = (float) ((k * yr + 16.) / 116.); + + if (zr > eps) + fz = (float) Math.pow(zr, 1 / 3.); + else + fz = (float) ((k * zr + 16.) / 116); + + float Ls = (116 * fy) - 16; + float as = 500 * (fx - fy); + float bs = 200 * (fy - fz); + + lab[0] = (2.55f * Ls + .5f); + lab[1] = (as + .5f); + lab[2] = (bs + .5f); + + return lab; + } + + /** + * Calculate the colour difference value between two colours in lab space. + * This code is from OpenIMAJ under BSD License + * + * @param L1 first colour's L component + * @param a1 first colour's a component + * @param b1 first colour's b component + * @param L2 second colour's L component + * @param a2 second colour's a component + * @param b2 second colour's b component + * @return the CIE 2000 colour difference + */ + public static double calculateDeltaE(double L1, double a1, double b1, + double L2, double a2, double b2) { + double Lmean = (L1 + L2) / 2.0; + double C1 = Math.sqrt(a1 * a1 + b1 * b1); + double C2 = Math.sqrt(a2 * a2 + b2 * b2); + double Cmean = (C1 + C2) / 2.0; + + double G = (1 - Math.sqrt(Math.pow(Cmean, 7) / (Math.pow(Cmean, 7) + Math.pow(25, 7)))) / 2; + double a1prime = a1 * (1 + G); + double a2prime = a2 * (1 + G); + + double C1prime = Math.sqrt(a1prime * a1prime + b1 * b1); + double C2prime = Math.sqrt(a2prime * a2prime + b2 * b2); + double Cmeanprime = (C1prime + C2prime) / 2; + + double h1prime = Math.atan2(b1, a1prime) + + 2 * Math.PI * (Math.atan2(b1, a1prime) < 0 ? 1 : 0); + double h2prime = Math.atan2(b2, a2prime) + + 2 * Math.PI * (Math.atan2(b2, a2prime) < 0 ? 1 : 0); + double Hmeanprime = ((Math.abs(h1prime - h2prime) > Math.PI) + ? (h1prime + h2prime + 2 * Math.PI) / 2 : (h1prime + h2prime) / 2); + + double T = 1.0 - 0.17 * Math.cos(Hmeanprime - Math.PI / 6.0) + + 0.24 * Math.cos(2 * Hmeanprime) + 0.32 * Math.cos(3 * Hmeanprime + Math.PI / 30) + - 0.2 * Math.cos(4 * Hmeanprime - 21 * Math.PI / 60); + + double deltahprime = ((Math.abs(h1prime - h2prime) <= Math.PI) ? h2prime - h1prime + : (h2prime <= h1prime) ? h2prime - h1prime + 2 * Math.PI + : h2prime - h1prime - 2 * Math.PI); + + double deltaLprime = L2 - L1; + double deltaCprime = C2prime - C1prime; + double deltaHprime = 2.0 * Math.sqrt(C1prime * C2prime) * Math.sin(deltahprime / 2.0); + double SL = 1.0 + ((0.015 * (Lmean - 50) * (Lmean - 50)) + / (Math.sqrt(20 + (Lmean - 50) * (Lmean - 50)))); + double SC = 1.0 + 0.045 * Cmeanprime; + double SH = 1.0 + 0.015 * Cmeanprime * T; + + double deltaTheta = (30 * Math.PI / 180) + * Math.exp(-((180 / Math.PI * Hmeanprime - 275) / 25) + * ((180 / Math.PI * Hmeanprime - 275) / 25)); + double RC = (2 + * Math.sqrt(Math.pow(Cmeanprime, 7) / (Math.pow(Cmeanprime, 7) + Math.pow(25, 7)))); + double RT = (-RC * Math.sin(2 * deltaTheta)); + + double KL = 1; + double KC = 1; + double KH = 1; + + double deltaE = Math.sqrt( + ((deltaLprime / (KL * SL)) * (deltaLprime / (KL * SL))) + + ((deltaCprime / (KC * SC)) * (deltaCprime / (KC * SC))) + + ((deltaHprime / (KH * SH)) * (deltaHprime / (KH * SH))) + + (RT * (deltaCprime / (KC * SC)) * (deltaHprime / (KH * SH)))); + + return deltaE; + } + + /** + * Finds the "perceptually nearest" color from a list of colors to + * the given RGB value. This is done by converting to + * L*a*b colorspace and using the CIE2000 deltaE algorithm. + * + * @param rgb The original color to start with + * @param colors An array of colors to test + * @return RGB packed int of nearest color in the list + */ + public static int findPerceptuallyNearestColor(int rgb, int[] colors) { + int nearestColor = 0; + double closest = Double.MAX_VALUE; + + float[] original = convertRGBtoLAB(rgb); + + for (int i = 0; i < colors.length; i++) { + float[] cl = convertRGBtoLAB(colors[i]); + double deltaE = calculateDeltaE(original[0], original[1], original[2], + cl[0], cl[1], cl[2]); + if (deltaE < closest) { + nearestColor = colors[i]; + closest = deltaE; + } + } + return nearestColor; + } + + /** + * Convenience method to find the nearest "solid" color (having RGB components + * of either 0 or 255) to the given color. This is useful for cases such as + * LED notification lights which may not be able to display the full range + * of colors due to hardware limitations. + * + * @param rgb + * @return the perceptually nearest color in RGB + */ + public static int findPerceptuallyNearestSolidColor(int rgb) { + return findPerceptuallyNearestColor(rgb, SOLID_COLORS); + } + + /** + * Given a Palette, pick out the dominant swatch based on population + * + * @param palette + * @return the dominant Swatch + */ + public static Palette.Swatch getDominantSwatch(Palette palette) { + if (palette == null || palette.getSwatches().size() == 0) { + return null; + } + // find most-represented swatch based on population + return Collections.max(palette.getSwatches(), new Comparator() { + @Override + public int compare(Palette.Swatch sw1, Palette.Swatch sw2) { + return Integer.compare(sw1.getPopulation(), sw2.getPopulation()); + } + }); + } + + /** + * Takes a drawable and uses Palette to generate a suitable "alert" + * color which can be used for an external notification mechanism + * such as an RGB LED. This will always pick a solid color having + * RGB components of 255 or 0. + * + * @param drawable The drawable to generate a color for + * @return a suitable solid color which corresponds to the image + */ + public static int generateAlertColorFromDrawable(Drawable drawable) { + int alertColor = Color.BLACK; + Bitmap bitmap = null; + + if (drawable == null) { + return alertColor; + } + + if (drawable instanceof BitmapDrawable) { + bitmap = ((BitmapDrawable) drawable).getBitmap(); + } else { + int width = drawable.getIntrinsicWidth(); + int height = drawable.getIntrinsicHeight(); + bitmap = Bitmap.createBitmap(Math.max(1, width), + Math.max(1, height), + Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + } + + if (bitmap != null) { + Palette p = Palette.from(bitmap).generate(); + if (p == null) { + return alertColor; + } + + // First try the dominant color + final Palette.Swatch dominantSwatch = getDominantSwatch(p); + int iconColor = alertColor; + if (dominantSwatch != null) { + iconColor = dominantSwatch.getRgb(); + alertColor = findPerceptuallyNearestSolidColor(iconColor); + } + + // Try the most saturated color if we got white or black (boring) + if (alertColor == Color.BLACK || alertColor == Color.WHITE) { + iconColor = p.getVibrantColor(Color.WHITE); + alertColor = findPerceptuallyNearestSolidColor(iconColor); + } + + if (!(drawable instanceof BitmapDrawable)) { + bitmap.recycle(); + } + } + + return alertColor; + } + + /** + * Convert a color temperature value (in Kelvin) to a RGB units as floats. + * This can be used in a transform matrix or hardware gamma control. + * + * @param degreesK + * @return array of floats representing rgb values 0->1 + */ + + public static float[] temperatureToRGB(int degreesK) { + float[] rgb = new float[3]; + + final float square = degreesK * degreesK; + for (int i = 0; i < rgb.length; i++) { + rgb[i] = square * COEFFICIENTS[i * 3] + + degreesK * COEFFICIENTS[i * 3 + 1] + COEFFICIENTS[i * 3 + 2]; + } + + return rgb; + } + + /** + * This table is a modified version of the original blackbody chart, found here: + * http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html + * + * Created by Ingo Thiel. + */ + private static final double[] sColorTable = new double[] { + 1.00000000, 0.18172716, 0.00000000, + 1.00000000, 0.25503671, 0.00000000, + 1.00000000, 0.30942099, 0.00000000, + 1.00000000, 0.35357379, 0.00000000, + 1.00000000, 0.39091524, 0.00000000, + 1.00000000, 0.42322816, 0.00000000, + 1.00000000, 0.45159884, 0.00000000, + 1.00000000, 0.47675916, 0.00000000, + 1.00000000, 0.49923747, 0.00000000, + 1.00000000, 0.51943421, 0.00000000, + 1.00000000, 0.54360078, 0.08679949, + 1.00000000, 0.56618736, 0.14065513, + 1.00000000, 0.58734976, 0.18362641, + 1.00000000, 0.60724493, 0.22137978, + 1.00000000, 0.62600248, 0.25591950, + 1.00000000, 0.64373109, 0.28819679, + 1.00000000, 0.66052319, 0.31873863, + 1.00000000, 0.67645822, 0.34786758, + 1.00000000, 0.69160518, 0.37579588, + 1.00000000, 0.70602449, 0.40267128, + 1.00000000, 0.71976951, 0.42860152, + 1.00000000, 0.73288760, 0.45366838, + 1.00000000, 0.74542112, 0.47793608, + 1.00000000, 0.75740814, 0.50145662, + 1.00000000, 0.76888303, 0.52427322, + 1.00000000, 0.77987699, 0.54642268, + 1.00000000, 0.79041843, 0.56793692, + 1.00000000, 0.80053332, 0.58884417, + 1.00000000, 0.81024551, 0.60916971, + 1.00000000, 0.81957693, 0.62893653, + 1.00000000, 0.82854786, 0.64816570, + 1.00000000, 0.83717703, 0.66687674, + 1.00000000, 0.84548188, 0.68508786, + 1.00000000, 0.85347859, 0.70281616, + 1.00000000, 0.86118227, 0.72007777, + 1.00000000, 0.86860704, 0.73688797, + 1.00000000, 0.87576611, 0.75326132, + 1.00000000, 0.88267187, 0.76921169, + 1.00000000, 0.88933596, 0.78475236, + 1.00000000, 0.89576933, 0.79989606, + 1.00000000, 0.90198230, 0.81465502, + 1.00000000, 0.90963069, 0.82838210, + 1.00000000, 0.91710889, 0.84190889, + 1.00000000, 0.92441842, 0.85523742, + 1.00000000, 0.93156127, 0.86836903, + 1.00000000, 0.93853986, 0.88130458, + 1.00000000, 0.94535695, 0.89404470, + 1.00000000, 0.95201559, 0.90658983, + 1.00000000, 0.95851906, 0.91894041, + 1.00000000, 0.96487079, 0.93109690, + 1.00000000, 0.97107439, 0.94305985, + 1.00000000, 0.97713351, 0.95482993, + 1.00000000, 0.98305189, 0.96640795, + 1.00000000, 0.98883326, 0.97779486, + 1.00000000, 0.99448139, 0.98899179, + 1.00000000, 1.00000000, 1.00000000, + 0.98947904, 0.99348723, 1.00000000, + 0.97940448, 0.98722715, 1.00000000, + 0.96975025, 0.98120637, 1.00000000, + 0.96049223, 0.97541240, 1.00000000, + 0.95160805, 0.96983355, 1.00000000, + 0.94303638, 0.96443333, 1.00000000, + 0.93480451, 0.95923080, 1.00000000, + 0.92689056, 0.95421394, 1.00000000, + 0.91927697, 0.94937330, 1.00000000, + 0.91194747, 0.94470005, 1.00000000, + 0.90488690, 0.94018594, 1.00000000, + 0.89808115, 0.93582323, 1.00000000, + 0.89151710, 0.93160469, 1.00000000, + 0.88518247, 0.92752354, 1.00000000, + 0.87906581, 0.92357340, 1.00000000, + 0.87315640, 0.91974827, 1.00000000, + 0.86744421, 0.91604254, 1.00000000, + 0.86191983, 0.91245088, 1.00000000, + 0.85657444, 0.90896831, 1.00000000, + 0.85139976, 0.90559011, 1.00000000, + 0.84638799, 0.90231183, 1.00000000, + 0.84153180, 0.89912926, 1.00000000, + 0.83682430, 0.89603843, 1.00000000, + 0.83225897, 0.89303558, 1.00000000, + 0.82782969, 0.89011714, 1.00000000, + 0.82353066, 0.88727974, 1.00000000, + 0.81935641, 0.88452017, 1.00000000, + 0.81530175, 0.88183541, 1.00000000, + 0.81136180, 0.87922257, 1.00000000, + 0.80753191, 0.87667891, 1.00000000, + 0.80380769, 0.87420182, 1.00000000, + 0.80018497, 0.87178882, 1.00000000, + 0.79665980, 0.86943756, 1.00000000, + 0.79322843, 0.86714579, 1.00000000, + 0.78988728, 0.86491137, 1.00000000, + 0.78663296, 0.86273225, 1.00000000, + 0.78346225, 0.86060650, 1.00000000, + 0.78037207, 0.85853224, 1.00000000, + 0.77735950, 0.85650771, 1.00000000, + 0.77442176, 0.85453121, 1.00000000, + 0.77155617, 0.85260112, 1.00000000, + 0.76876022, 0.85071588, 1.00000000, + 0.76603147, 0.84887402, 1.00000000, + 0.76336762, 0.84707411, 1.00000000, + 0.76076645, 0.84531479, 1.00000000, + 0.75822586, 0.84359476, 1.00000000, + 0.75574383, 0.84191277, 1.00000000, + 0.75331843, 0.84026762, 1.00000000, + 0.75094780, 0.83865816, 1.00000000, + 0.74863017, 0.83708329, 1.00000000, + 0.74636386, 0.83554194, 1.00000000, + 0.74414722, 0.83403311, 1.00000000, + 0.74197871, 0.83255582, 1.00000000, + 0.73985682, 0.83110912, 1.00000000, + 0.73778012, 0.82969211, 1.00000000, + 0.73574723, 0.82830393, 1.00000000, + 0.73375683, 0.82694373, 1.00000000, + 0.73180765, 0.82561071, 1.00000000, + 0.72989845, 0.82430410, 1.00000000, + 0.72802807, 0.82302316, 1.00000000, + 0.72619537, 0.82176715, 1.00000000, + 0.72439927, 0.82053539, 1.00000000, + 0.72263872, 0.81932722, 1.00000000, + 0.72091270, 0.81814197, 1.00000000, + 0.71922025, 0.81697905, 1.00000000, + 0.71756043, 0.81583783, 1.00000000, + 0.71593234, 0.81471775, 1.00000000, + 0.71433510, 0.81361825, 1.00000000, + 0.71276788, 0.81253878, 1.00000000, + 0.71122987, 0.81147883, 1.00000000, + 0.70972029, 0.81043789, 1.00000000, + 0.70823838, 0.80941546, 1.00000000, + 0.70678342, 0.80841109, 1.00000000, + 0.70535469, 0.80742432, 1.00000000, + 0.70395153, 0.80645469, 1.00000000, + 0.70257327, 0.80550180, 1.00000000, + 0.70121928, 0.80456522, 1.00000000, + 0.69988894, 0.80364455, 1.00000000, + 0.69858167, 0.80273941, 1.00000000, + 0.69729688, 0.80184943, 1.00000000, + 0.69603402, 0.80097423, 1.00000000, + 0.69479255, 0.80011347, 1.00000000, + 0.69357196, 0.79926681, 1.00000000, + 0.69237173, 0.79843391, 1.00000000, + 0.69119138, 0.79761446, 1.00000000, + 0.69003044, 0.79680814, 1.00000000, + 0.68888844, 0.79601466, 1.00000000, + 0.68776494, 0.79523371, 1.00000000, + 0.68665951, 0.79446502, 1.00000000, + 0.68557173, 0.79370830, 1.00000000, + 0.68450119, 0.79296330, 1.00000000, + 0.68344751, 0.79222975, 1.00000000, + 0.68241029, 0.79150740, 1.00000000, + 0.68138918, 0.79079600, 1.00000000, + 0.68038380, 0.79009531, 1.00000000, + 0.67939381, 0.78940511, 1.00000000, + 0.67841888, 0.78872517, 1.00000000, + 0.67745866, 0.78805526, 1.00000000, + 0.67651284, 0.78739518, 1.00000000, + 0.67558112, 0.78674472, 1.00000000, + 0.67466317, 0.78610368, 1.00000000, + 0.67375872, 0.78547186, 1.00000000, + 0.67286748, 0.78484907, 1.00000000, + 0.67198916, 0.78423512, 1.00000000, + 0.67112350, 0.78362984, 1.00000000, + 0.67027024, 0.78303305, 1.00000000, + 0.66942911, 0.78244457, 1.00000000, + 0.66859988, 0.78186425, 1.00000000, + 0.66778228, 0.78129191, 1.00000000, + 0.66697610, 0.78072740, 1.00000000, + 0.66618110, 0.78017057, 1.00000000, + 0.66539706, 0.77962127, 1.00000000, + 0.66462376, 0.77907934, 1.00000000, + 0.66386098, 0.77854465, 1.00000000, + 0.66310852, 0.77801705, 1.00000000, + 0.66236618, 0.77749642, 1.00000000, + 0.66163375, 0.77698261, 1.00000000, + 0.66091106, 0.77647551, 1.00000000, + 0.66019791, 0.77597498, 1.00000000, + 0.65949412, 0.77548090, 1.00000000, + 0.65879952, 0.77499315, 1.00000000, + 0.65811392, 0.77451161, 1.00000000, + 0.65743716, 0.77403618, 1.00000000, + 0.65676908, 0.77356673, 1.00000000, + 0.65610952, 0.77310316, 1.00000000, + 0.65545831, 0.77264537, 1.00000000, + 0.65481530, 0.77219324, 1.00000000, + 0.65418036, 0.77174669, 1.00000000, + 0.65355332, 0.77130560, 1.00000000, + 0.65293404, 0.77086988, 1.00000000, + 0.65232240, 0.77043944, 1.00000000, + 0.65171824, 0.77001419, 1.00000000, + 0.65112144, 0.76959404, 1.00000000, + 0.65053187, 0.76917889, 1.00000000, + 0.64994941, 0.76876866, 1.00000000, + 0.64937392, 0.76836326, 1.00000000 + }; + +} diff --git a/core/java/com/android/internal/lineage/util/Concierge.java b/core/java/com/android/internal/lineage/util/Concierge.java new file mode 100644 index 000000000000..4fffe7d5107a --- /dev/null +++ b/core/java/com/android/internal/lineage/util/Concierge.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2016 The CyanogenMod 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.lineage.util; + +import android.os.Parcel; + +/** + * Simply, Concierge handles your parcels and makes sure they get marshalled and unmarshalled + * correctly when cross IPC boundaries even when there is a version mismatch between the client + * sdk level and the framework implementation. + * + *

    On incoming parcel (to be unmarshalled): + * + *

    + *     ParcelInfo incomingParcelInfo = Concierge.receiveParcel(incomingParcel);
    + *     int parcelableVersion = incomingParcelInfo.getParcelVersion();
    + *
    + *     // Do unmarshalling steps here iterating over every plausible version
    + *
    + *     // Complete the process
    + *     incomingParcelInfo.complete();
    + * 
    + * + *

    On outgoing parcel (to be marshalled): + * + *

    + *     ParcelInfo outgoingParcelInfo = Concierge.prepareParcel(incomingParcel);
    + *
    + *     // Do marshalling steps here iterating over every plausible version
    + *
    + *     // Complete the process
    + *     outgoingParcelInfo.complete();
    + * 
    + */ +public final class Concierge { + + /** Not instantiable */ + private Concierge() { + // Don't instantiate + } + + /** + * Since there might be a case where new versions of the lineage framework use applications running + * old versions of the protocol (and thus old versions of this class), we need a versioning + * system for the parcels sent between the core framework and its sdk users. + * + * This parcelable version should be the latest version API version listed in + * {@link LINEAGE_VERSION_CODES} + * @hide + */ + public static final int PARCELABLE_VERSION = 9; + + /** + * Tell the concierge to receive our parcel, so we can get information from it. + * + * MUST CALL {@link ParcelInfo#complete()} AFTER UNMARSHALLING. + * + * @param parcel Incoming parcel to be unmarshalled + * @return {@link ParcelInfo} containing parcel information, specifically the version. + */ + public static ParcelInfo receiveParcel(Parcel parcel) { + return new ParcelInfo(parcel); + } + + /** + * Prepare a parcel for the Concierge. + * + * MUST CALL {@link ParcelInfo#complete()} AFTER MARSHALLING. + * + * @param parcel Outgoing parcel to be marshalled + * @return {@link ParcelInfo} containing parcel information, specifically the version. + */ + public static ParcelInfo prepareParcel(Parcel parcel) { + return new ParcelInfo(parcel, PARCELABLE_VERSION); + } + + /** + * Parcel header info specific to the Parcel object that is passed in via + * {@link #prepareParcel(Parcel)} or {@link #receiveParcel(Parcel)}. The exposed method + * of {@link #getParcelVersion()} gets the api level of the parcel object. + */ + public final static class ParcelInfo { + private Parcel mParcel; + private int mParcelableVersion; + private int mParcelableSize; + private int mStartPosition; + private int mSizePosition; + private boolean mCreation = false; + + ParcelInfo(Parcel parcel) { + mCreation = false; + mParcel = parcel; + mParcelableVersion = parcel.readInt(); + mParcelableSize = parcel.readInt(); + mStartPosition = parcel.dataPosition(); + } + + ParcelInfo(Parcel parcel, int parcelableVersion) { + mCreation = true; + mParcel = parcel; + mParcelableVersion = parcelableVersion; + + // Write parcelable version, make sure to define explicit changes + // within {@link #PARCELABLE_VERSION); + mParcel.writeInt(mParcelableVersion); + + // Inject a placeholder that will store the parcel size from this point on + // (not including the size itself). + mSizePosition = parcel.dataPosition(); + mParcel.writeInt(0); + mStartPosition = parcel.dataPosition(); + } + + /** + * Get the parcel version from the {@link Parcel} received by the Concierge. + * @return {@link #PARCELABLE_VERSION} of the {@link Parcel} + */ + public int getParcelVersion() { + return mParcelableVersion; + } + + /** + * Complete the {@link ParcelInfo} for the Concierge. + */ + public void complete() { + if (mCreation) { + // Go back and write size + mParcelableSize = mParcel.dataPosition() - mStartPosition; + mParcel.setDataPosition(mSizePosition); + mParcel.writeInt(mParcelableSize); + mParcel.setDataPosition(mStartPosition + mParcelableSize); + } else { + mParcel.setDataPosition(mStartPosition + mParcelableSize); + } + } + } +} diff --git a/core/java/com/android/internal/lineage/util/MathUtils.java b/core/java/com/android/internal/lineage/util/MathUtils.java new file mode 100644 index 000000000000..6cab1063efd6 --- /dev/null +++ b/core/java/com/android/internal/lineage/util/MathUtils.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 The CyanogenMod 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.lineage.util; + +public final class MathUtils { + + /** + * Given a range of values which change continuously in a non-linear way, + * we can map back and forth to a linear scale using some quadratic equations. + * + * The linear scale ranges from 0 -> 1. This method will calculate the + * coefficients needed to solve the conversion functions in the next two methods. + * + * lower = actual value when linear value = 0 + * mid = actual value when linear value = .5 + * upper actual value when linear value = 1 + * + * @param lower + * @param mid + * @param upper + * @return array of coefficients + */ + public static double[] powerCurve(double lower, double mid, double upper) { + final double[] curve = new double[3]; + curve[0] = ((lower * upper) - (mid * mid)) / (lower - (2 * mid) + upper); + curve[1] = Math.pow((mid - lower), 2) / (lower - (2 * mid) + upper); + curve[2] = 2 * Math.log((upper - mid) / (mid - lower)); + return curve; + } + + /** + * Map a value on a power curve to a linear value + * + * @param curve obtained from powerCurve() + * @param value to convert to linear scale + * @return linear value from 0 -> 1 + */ + public static double powerCurveToLinear(final double[] curve, double value) { + return Math.log((value - curve[0]) / curve[1]) / curve[2]; + } + + /** + * Map a value on a linear scale to a value on a power curve + * + * @param curve obtained from powerCurve() + * @param value from 0 -> 1 to map onto power curve + * @return actual value on the given curve + */ + public static double linearToPowerCurve(final double[] curve, double value) { + return curve[0] + curve[1] * Math.exp(curve[2] * value); + } + + +} diff --git a/core/java/com/android/internal/lineage/util/palette/ColorCutQuantizer.java b/core/java/com/android/internal/lineage/util/palette/ColorCutQuantizer.java new file mode 100644 index 000000000000..308d455b4998 --- /dev/null +++ b/core/java/com/android/internal/lineage/util/palette/ColorCutQuantizer.java @@ -0,0 +1,517 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.lineage.util.palette; + +import android.graphics.Color; +import android.util.TimingLogger; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.PriorityQueue; + +import com.android.internal.lineage.util.palette.Palette.Swatch; + +/** + * An color quantizer based on the Median-cut algorithm, but optimized for picking out distinct + * colors rather than representation colors. + * + * The color space is represented as a 3-dimensional cube with each dimension being an RGB + * component. The cube is then repeatedly divided until we have reduced the color space to the + * requested number of colors. An average color is then generated from each cube. + * + * What makes this different to median-cut is that median-cut divided cubes so that all of the cubes + * have roughly the same population, where this quantizer divides boxes based on their color volume. + * This means that the color space is divided into distinct colors, rather than representative + * colors. + * + * @hide + */ +final class ColorCutQuantizer { + + private static final String LOG_TAG = "ColorCutQuantizer"; + private static final boolean LOG_TIMINGS = false; + + private static final int COMPONENT_RED = -3; + private static final int COMPONENT_GREEN = -2; + private static final int COMPONENT_BLUE = -1; + + private static final int QUANTIZE_WORD_WIDTH = 5; + private static final int QUANTIZE_WORD_MASK = (1 << QUANTIZE_WORD_WIDTH) - 1; + + final int[] mColors; + final int[] mHistogram; + final List mQuantizedColors; + final TimingLogger mTimingLogger; + final Palette.Filter[] mFilters; + + private final float[] mTempHsl = new float[3]; + + /** + * Constructor. + * + * @param pixels histogram representing an image's pixel data + * @param maxColors The maximum number of colors that should be in the result palette. + * @param filters Set of filters to use in the quantization stage + */ + ColorCutQuantizer(final int[] pixels, final int maxColors, final Palette.Filter[] filters) { + mTimingLogger = LOG_TIMINGS ? new TimingLogger(LOG_TAG, "Creation") : null; + mFilters = filters; + + final int[] hist = mHistogram = new int[1 << (QUANTIZE_WORD_WIDTH * 3)]; + for (int i = 0; i < pixels.length; i++) { + final int quantizedColor = quantizeFromRgb888(pixels[i]); + // Now update the pixel value to the quantized value + pixels[i] = quantizedColor; + // And update the histogram + hist[quantizedColor]++; + } + + if (LOG_TIMINGS) { + mTimingLogger.addSplit("Histogram created"); + } + + // Now let's count the number of distinct colors + int distinctColorCount = 0; + for (int color = 0; color < hist.length; color++) { + if (hist[color] > 0 && shouldIgnoreColor(color)) { + // If we should ignore the color, set the population to 0 + hist[color] = 0; + } + if (hist[color] > 0) { + // If the color has population, increase the distinct color count + distinctColorCount++; + } + } + + if (LOG_TIMINGS) { + mTimingLogger.addSplit("Filtered colors and distinct colors counted"); + } + + // Now lets go through create an array consisting of only distinct colors + final int[] colors = mColors = new int[distinctColorCount]; + int distinctColorIndex = 0; + for (int color = 0; color < hist.length; color++) { + if (hist[color] > 0) { + colors[distinctColorIndex++] = color; + } + } + + if (LOG_TIMINGS) { + mTimingLogger.addSplit("Distinct colors copied into array"); + } + + if (distinctColorCount <= maxColors) { + // The image has fewer colors than the maximum requested, so just return the colors + mQuantizedColors = new ArrayList<>(); + for (int color : colors) { + mQuantizedColors.add(new Swatch(approximateToRgb888(color), hist[color])); + } + + if (LOG_TIMINGS) { + mTimingLogger.addSplit("Too few colors present. Copied to Swatches"); + mTimingLogger.dumpToLog(); + } + } else { + // We need use quantization to reduce the number of colors + mQuantizedColors = quantizePixels(maxColors); + + if (LOG_TIMINGS) { + mTimingLogger.addSplit("Quantized colors computed"); + mTimingLogger.dumpToLog(); + } + } + } + + /** + * @return the list of quantized colors + */ + List getQuantizedColors() { + return mQuantizedColors; + } + + private List quantizePixels(int maxColors) { + // Create the priority queue which is sorted by volume descending. This means we always + // split the largest box in the queue + final PriorityQueue pq = new PriorityQueue<>(maxColors, VBOX_COMPARATOR_VOLUME); + + // To start, offer a box which contains all of the colors + pq.offer(new Vbox(0, mColors.length - 1)); + + // Now go through the boxes, splitting them until we have reached maxColors or there are no + // more boxes to split + splitBoxes(pq, maxColors); + + // Finally, return the average colors of the color boxes + return generateAverageColors(pq); + } + + /** + * Iterate through the {@link java.util.Queue}, popping + * {@link ColorCutQuantizer.Vbox} objects from the queue + * and splitting them. Once split, the new box and the remaining box are offered back to the + * queue. + * + * @param queue {@link java.util.PriorityQueue} to poll for boxes + * @param maxSize Maximum amount of boxes to split + */ + private void splitBoxes(final PriorityQueue queue, final int maxSize) { + while (queue.size() < maxSize) { + final Vbox vbox = queue.poll(); + + if (vbox != null && vbox.canSplit()) { + // First split the box, and offer the result + queue.offer(vbox.splitBox()); + + if (LOG_TIMINGS) { + mTimingLogger.addSplit("Box split"); + } + // Then offer the box back + queue.offer(vbox); + } else { + if (LOG_TIMINGS) { + mTimingLogger.addSplit("All boxes split"); + } + // If we get here then there are no more boxes to split, so return + return; + } + } + } + + private List generateAverageColors(Collection vboxes) { + ArrayList colors = new ArrayList<>(vboxes.size()); + for (Vbox vbox : vboxes) { + Swatch swatch = vbox.getAverageColor(); + if (!shouldIgnoreColor(swatch)) { + // As we're averaging a color box, we can still get colors which we do not want, so + // we check again here + colors.add(swatch); + } + } + return colors; + } + + /** + * Represents a tightly fitting box around a color space. + */ + private class Vbox { + // lower and upper index are inclusive + private int mLowerIndex; + private int mUpperIndex; + // Population of colors within this box + private int mPopulation; + + private int mMinRed, mMaxRed; + private int mMinGreen, mMaxGreen; + private int mMinBlue, mMaxBlue; + + Vbox(int lowerIndex, int upperIndex) { + mLowerIndex = lowerIndex; + mUpperIndex = upperIndex; + fitBox(); + } + + final int getVolume() { + return (mMaxRed - mMinRed + 1) * (mMaxGreen - mMinGreen + 1) * + (mMaxBlue - mMinBlue + 1); + } + + final boolean canSplit() { + return getColorCount() > 1; + } + + final int getColorCount() { + return 1 + mUpperIndex - mLowerIndex; + } + + /** + * Recomputes the boundaries of this box to tightly fit the colors within the box. + */ + final void fitBox() { + final int[] colors = mColors; + final int[] hist = mHistogram; + + // Reset the min and max to opposite values + int minRed, minGreen, minBlue; + minRed = minGreen = minBlue = Integer.MAX_VALUE; + int maxRed, maxGreen, maxBlue; + maxRed = maxGreen = maxBlue = Integer.MIN_VALUE; + int count = 0; + + for (int i = mLowerIndex; i <= mUpperIndex; i++) { + final int color = colors[i]; + count += hist[color]; + + final int r = quantizedRed(color); + final int g = quantizedGreen(color); + final int b = quantizedBlue(color); + if (r > maxRed) { + maxRed = r; + } + if (r < minRed) { + minRed = r; + } + if (g > maxGreen) { + maxGreen = g; + } + if (g < minGreen) { + minGreen = g; + } + if (b > maxBlue) { + maxBlue = b; + } + if (b < minBlue) { + minBlue = b; + } + } + + mMinRed = minRed; + mMaxRed = maxRed; + mMinGreen = minGreen; + mMaxGreen = maxGreen; + mMinBlue = minBlue; + mMaxBlue = maxBlue; + mPopulation = count; + } + + /** + * Split this color box at the mid-point along it's longest dimension + * + * @return the new ColorBox + */ + final Vbox splitBox() { + if (!canSplit()) { + throw new IllegalStateException("Can not split a box with only 1 color"); + } + + // find median along the longest dimension + final int splitPoint = findSplitPoint(); + + Vbox newBox = new Vbox(splitPoint + 1, mUpperIndex); + + // Now change this box's upperIndex and recompute the color boundaries + mUpperIndex = splitPoint; + fitBox(); + + return newBox; + } + + /** + * @return the dimension which this box is largest in + */ + final int getLongestColorDimension() { + final int redLength = mMaxRed - mMinRed; + final int greenLength = mMaxGreen - mMinGreen; + final int blueLength = mMaxBlue - mMinBlue; + + if (redLength >= greenLength && redLength >= blueLength) { + return COMPONENT_RED; + } else if (greenLength >= redLength && greenLength >= blueLength) { + return COMPONENT_GREEN; + } else { + return COMPONENT_BLUE; + } + } + + /** + * Finds the point within this box's lowerIndex and upperIndex index of where to split. + * + * This is calculated by finding the longest color dimension, and then sorting the + * sub-array based on that dimension value in each color. The colors are then iterated over + * until a color is found with at least the midpoint of the whole box's dimension midpoint. + * + * @return the index of the colors array to split from + */ + final int findSplitPoint() { + final int longestDimension = getLongestColorDimension(); + final int[] colors = mColors; + final int[] hist = mHistogram; + + // We need to sort the colors in this box based on the longest color dimension. + // As we can't use a Comparator to define the sort logic, we modify each color so that + // it's most significant is the desired dimension + modifySignificantOctet(colors, longestDimension, mLowerIndex, mUpperIndex); + + // Now sort... Arrays.sort uses a exclusive toIndex so we need to add 1 + Arrays.sort(colors, mLowerIndex, mUpperIndex + 1); + + // Now revert all of the colors so that they are packed as RGB again + modifySignificantOctet(colors, longestDimension, mLowerIndex, mUpperIndex); + + final int midPoint = mPopulation / 2; + for (int i = mLowerIndex, count = 0; i <= mUpperIndex; i++) { + count += hist[colors[i]]; + if (count >= midPoint) { + return i; + } + } + + return mLowerIndex; + } + + /** + * @return the average color of this box. + */ + final Swatch getAverageColor() { + final int[] colors = mColors; + final int[] hist = mHistogram; + int redSum = 0; + int greenSum = 0; + int blueSum = 0; + int totalPopulation = 0; + + for (int i = mLowerIndex; i <= mUpperIndex; i++) { + final int color = colors[i]; + final int colorPopulation = hist[color]; + + totalPopulation += colorPopulation; + redSum += colorPopulation * quantizedRed(color); + greenSum += colorPopulation * quantizedGreen(color); + blueSum += colorPopulation * quantizedBlue(color); + } + + final int redMean = Math.round(redSum / (float) totalPopulation); + final int greenMean = Math.round(greenSum / (float) totalPopulation); + final int blueMean = Math.round(blueSum / (float) totalPopulation); + + return new Swatch(approximateToRgb888(redMean, greenMean, blueMean), totalPopulation); + } + } + + /** + * Modify the significant octet in a packed color int. Allows sorting based on the value of a + * single color component. This relies on all components being the same word size. + * + * @see Vbox#findSplitPoint() + */ + private static void modifySignificantOctet(final int[] a, final int dimension, + final int lower, final int upper) { + switch (dimension) { + case COMPONENT_RED: + // Already in RGB, no need to do anything + break; + case COMPONENT_GREEN: + // We need to do a RGB to GRB swap, or vice-versa + for (int i = lower; i <= upper; i++) { + final int color = a[i]; + a[i] = quantizedGreen(color) << (QUANTIZE_WORD_WIDTH + QUANTIZE_WORD_WIDTH) + | quantizedRed(color) << QUANTIZE_WORD_WIDTH + | quantizedBlue(color); + } + break; + case COMPONENT_BLUE: + // We need to do a RGB to BGR swap, or vice-versa + for (int i = lower; i <= upper; i++) { + final int color = a[i]; + a[i] = quantizedBlue(color) << (QUANTIZE_WORD_WIDTH + QUANTIZE_WORD_WIDTH) + | quantizedGreen(color) << QUANTIZE_WORD_WIDTH + | quantizedRed(color); + } + break; + } + } + + private boolean shouldIgnoreColor(int color565) { + final int rgb = approximateToRgb888(color565); + ColorUtils.colorToHSL(rgb, mTempHsl); + return shouldIgnoreColor(rgb, mTempHsl); + } + + private boolean shouldIgnoreColor(Swatch color) { + return shouldIgnoreColor(color.getRgb(), color.getHsl()); + } + + private boolean shouldIgnoreColor(int rgb, float[] hsl) { + if (mFilters != null && mFilters.length > 0) { + for (int i = 0, count = mFilters.length; i < count; i++) { + if (!mFilters[i].isAllowed(rgb, hsl)) { + return true; + } + } + } + return false; + } + + /** + * Comparator which sorts {@link Vbox} instances based on their volume, in descending order + */ + private static final Comparator VBOX_COMPARATOR_VOLUME = new Comparator() { + @Override + public int compare(Vbox lhs, Vbox rhs) { + return rhs.getVolume() - lhs.getVolume(); + } + }; + + /** + * Quantized a RGB888 value to have a word width of {@value #QUANTIZE_WORD_WIDTH}. + */ + private static int quantizeFromRgb888(int color) { + int r = modifyWordWidth(Color.red(color), 8, QUANTIZE_WORD_WIDTH); + int g = modifyWordWidth(Color.green(color), 8, QUANTIZE_WORD_WIDTH); + int b = modifyWordWidth(Color.blue(color), 8, QUANTIZE_WORD_WIDTH); + return r << (QUANTIZE_WORD_WIDTH + QUANTIZE_WORD_WIDTH) | g << QUANTIZE_WORD_WIDTH | b; + } + + /** + * Quantized RGB888 values to have a word width of {@value #QUANTIZE_WORD_WIDTH}. + */ + private static int approximateToRgb888(int r, int g, int b) { + return Color.rgb(modifyWordWidth(r, QUANTIZE_WORD_WIDTH, 8), + modifyWordWidth(g, QUANTIZE_WORD_WIDTH, 8), + modifyWordWidth(b, QUANTIZE_WORD_WIDTH, 8)); + } + + private static int approximateToRgb888(int color) { + return approximateToRgb888(quantizedRed(color), quantizedGreen(color), quantizedBlue(color)); + } + + /** + * @return red component of the quantized color + */ + private static int quantizedRed(int color) { + return (color >> (QUANTIZE_WORD_WIDTH + QUANTIZE_WORD_WIDTH)) & QUANTIZE_WORD_MASK; + } + + /** + * @return green component of a quantized color + */ + private static int quantizedGreen(int color) { + return (color >> QUANTIZE_WORD_WIDTH) & QUANTIZE_WORD_MASK; + } + + /** + * @return blue component of a quantized color + */ + private static int quantizedBlue(int color) { + return color & QUANTIZE_WORD_MASK; + } + + private static int modifyWordWidth(int value, int currentWidth, int targetWidth) { + final int newValue; + if (targetWidth > currentWidth) { + // If we're approximating up in word width, we'll shift up + newValue = value << (targetWidth - currentWidth); + } else { + // Else, we will just shift and keep the MSB + newValue = value >> (currentWidth - targetWidth); + } + return newValue & ((1 << targetWidth) - 1); + } + +} diff --git a/core/java/com/android/internal/lineage/util/palette/ColorUtils.java b/core/java/com/android/internal/lineage/util/palette/ColorUtils.java new file mode 100644 index 000000000000..0b79e44c4d91 --- /dev/null +++ b/core/java/com/android/internal/lineage/util/palette/ColorUtils.java @@ -0,0 +1,299 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.lineage.util.palette; + +import android.graphics.Color; + +/** + * A set of color-related utility methods, building upon those available in {@code Color}. + * + * @hide + */ +public class ColorUtils { + + private static final int MIN_ALPHA_SEARCH_MAX_ITERATIONS = 10; + private static final int MIN_ALPHA_SEARCH_PRECISION = 10; + + private ColorUtils() {} + + /** + * Composite two potentially translucent colors over each other and returns the result. + */ + public static int compositeColors(int foreground, int background) { + int bgAlpha = Color.alpha(background); + int fgAlpha = Color.alpha(foreground); + int a = compositeAlpha(fgAlpha, bgAlpha); + + int r = compositeComponent(Color.red(foreground), fgAlpha, + Color.red(background), bgAlpha, a); + int g = compositeComponent(Color.green(foreground), fgAlpha, + Color.green(background), bgAlpha, a); + int b = compositeComponent(Color.blue(foreground), fgAlpha, + Color.blue(background), bgAlpha, a); + + return Color.argb(a, r, g, b); + } + + private static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) { + return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF); + } + + private static int compositeComponent(int fgC, int fgA, int bgC, int bgA, int a) { + if (a == 0) return 0; + return ((0xFF * fgC * fgA) + (bgC * bgA * (0xFF - fgA))) / (a * 0xFF); + } + + /** + * Returns the luminance of a color. + * + * Formula defined here: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef + */ + public static double calculateLuminance(int color) { + double red = Color.red(color) / 255d; + red = red < 0.03928 ? red / 12.92 : Math.pow((red + 0.055) / 1.055, 2.4); + + double green = Color.green(color) / 255d; + green = green < 0.03928 ? green / 12.92 : Math.pow((green + 0.055) / 1.055, 2.4); + + double blue = Color.blue(color) / 255d; + blue = blue < 0.03928 ? blue / 12.92 : Math.pow((blue + 0.055) / 1.055, 2.4); + + return (0.2126 * red) + (0.7152 * green) + (0.0722 * blue); + } + + /** + * Returns the contrast ratio between {@code foreground} and {@code background}. + * {@code background} must be opaque. + *

    + * Formula defined + * here. + */ + public static double calculateContrast(int foreground, int background) { + if (Color.alpha(background) != 255) { + throw new IllegalArgumentException("background can not be translucent"); + } + if (Color.alpha(foreground) < 255) { + // If the foreground is translucent, composite the foreground over the background + foreground = compositeColors(foreground, background); + } + + final double luminance1 = calculateLuminance(foreground) + 0.05; + final double luminance2 = calculateLuminance(background) + 0.05; + + // Now return the lighter luminance divided by the darker luminance + return Math.max(luminance1, luminance2) / Math.min(luminance1, luminance2); + } + + /** + * Calculates the minimum alpha value which can be applied to {@code foreground} so that would + * have a contrast value of at least {@code minContrastRatio} when compared to + * {@code background}. + * + * @param foreground the foreground color. + * @param background the background color. Should be opaque. + * @param minContrastRatio the minimum contrast ratio. + * @return the alpha value in the range 0-255, or -1 if no value could be calculated. + */ + public static int calculateMinimumAlpha(int foreground, int background, + float minContrastRatio) { + if (Color.alpha(background) != 255) { + throw new IllegalArgumentException("background can not be translucent"); + } + + // First lets check that a fully opaque foreground has sufficient contrast + int testForeground = setAlphaComponent(foreground, 255); + double testRatio = calculateContrast(testForeground, background); + if (testRatio < minContrastRatio) { + // Fully opaque foreground does not have sufficient contrast, return error + return -1; + } + + // Binary search to find a value with the minimum value which provides sufficient contrast + int numIterations = 0; + int minAlpha = 0; + int maxAlpha = 255; + + while (numIterations <= MIN_ALPHA_SEARCH_MAX_ITERATIONS && + (maxAlpha - minAlpha) > MIN_ALPHA_SEARCH_PRECISION) { + final int testAlpha = (minAlpha + maxAlpha) / 2; + + testForeground = setAlphaComponent(foreground, testAlpha); + testRatio = calculateContrast(testForeground, background); + + if (testRatio < minContrastRatio) { + minAlpha = testAlpha; + } else { + maxAlpha = testAlpha; + } + + numIterations++; + } + + // Conservatively return the max of the range of possible alphas, which is known to pass. + return maxAlpha; + } + + /** + * Convert RGB components to HSL (hue-saturation-lightness). + *

      + *
    • hsl[0] is Hue [0 .. 360)
    • + *
    • hsl[1] is Saturation [0...1]
    • + *
    • hsl[2] is Lightness [0...1]
    • + *
    + * + * @param r red component value [0..255] + * @param g green component value [0..255] + * @param b blue component value [0..255] + * @param hsl 3 element array which holds the resulting HSL components. + */ + public static void RGBToHSL(int r, int g, int b, float[] hsl) { + final float rf = r / 255f; + final float gf = g / 255f; + final float bf = b / 255f; + + final float max = Math.max(rf, Math.max(gf, bf)); + final float min = Math.min(rf, Math.min(gf, bf)); + final float deltaMaxMin = max - min; + + float h, s; + float l = (max + min) / 2f; + + if (max == min) { + // Monochromatic + h = s = 0f; + } else { + if (max == rf) { + h = ((gf - bf) / deltaMaxMin) % 6f; + } else if (max == gf) { + h = ((bf - rf) / deltaMaxMin) + 2f; + } else { + h = ((rf - gf) / deltaMaxMin) + 4f; + } + + s = deltaMaxMin / (1f - Math.abs(2f * l - 1f)); + } + + h = (h * 60f) % 360f; + if (h < 0) { + h += 360f; + } + + hsl[0] = constrain(h, 0f, 360f); + hsl[1] = constrain(s, 0f, 1f); + hsl[2] = constrain(l, 0f, 1f); + } + + /** + * Convert the ARGB color to its HSL (hue-saturation-lightness) components. + *
      + *
    • hsl[0] is Hue [0 .. 360)
    • + *
    • hsl[1] is Saturation [0...1]
    • + *
    • hsl[2] is Lightness [0...1]
    • + *
    + * + * @param color the ARGB color to convert. The alpha component is ignored. + * @param hsl 3 element array which holds the resulting HSL components. + */ + public static void colorToHSL(int color, float[] hsl) { + RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hsl); + } + + /** + * Convert HSL (hue-saturation-lightness) components to a RGB color. + *
      + *
    • hsl[0] is Hue [0 .. 360)
    • + *
    • hsl[1] is Saturation [0...1]
    • + *
    • hsl[2] is Lightness [0...1]
    • + *
    + * If hsv values are out of range, they are pinned. + * + * @param hsl 3 element array which holds the input HSL components. + * @return the resulting RGB color + */ + public static int HSLToColor(float[] hsl) { + final float h = hsl[0]; + final float s = hsl[1]; + final float l = hsl[2]; + + final float c = (1f - Math.abs(2 * l - 1f)) * s; + final float m = l - 0.5f * c; + final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f)); + + final int hueSegment = (int) h / 60; + + int r = 0, g = 0, b = 0; + + switch (hueSegment) { + case 0: + r = Math.round(255 * (c + m)); + g = Math.round(255 * (x + m)); + b = Math.round(255 * m); + break; + case 1: + r = Math.round(255 * (x + m)); + g = Math.round(255 * (c + m)); + b = Math.round(255 * m); + break; + case 2: + r = Math.round(255 * m); + g = Math.round(255 * (c + m)); + b = Math.round(255 * (x + m)); + break; + case 3: + r = Math.round(255 * m); + g = Math.round(255 * (x + m)); + b = Math.round(255 * (c + m)); + break; + case 4: + r = Math.round(255 * (x + m)); + g = Math.round(255 * m); + b = Math.round(255 * (c + m)); + break; + case 5: + case 6: + r = Math.round(255 * (c + m)); + g = Math.round(255 * m); + b = Math.round(255 * (x + m)); + break; + } + + r = constrain(r, 0, 255); + g = constrain(g, 0, 255); + b = constrain(b, 0, 255); + + return Color.rgb(r, g, b); + } + + /** + * Set the alpha component of {@code color} to be {@code alpha}. + */ + public static int setAlphaComponent(int color, int alpha) { + if (alpha < 0 || alpha > 255) { + throw new IllegalArgumentException("alpha must be between 0 and 255."); + } + return (color & 0x00ffffff) | (alpha << 24); + } + + private static float constrain(float amount, float low, float high) { + return amount < low ? low : (amount > high ? high : amount); + } + + private static int constrain(int amount, int low, int high) { + return amount < low ? low : (amount > high ? high : amount); + } + +} diff --git a/core/java/com/android/internal/lineage/util/palette/DefaultGenerator.java b/core/java/com/android/internal/lineage/util/palette/DefaultGenerator.java new file mode 100644 index 000000000000..a3b663a573cc --- /dev/null +++ b/core/java/com/android/internal/lineage/util/palette/DefaultGenerator.java @@ -0,0 +1,244 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.lineage.util.palette; + +import java.util.List; + +import com.android.internal.lineage.util.palette.Palette.Swatch; + +/** + * @hide + */ +class DefaultGenerator extends Palette.Generator { + + private static final float TARGET_DARK_LUMA = 0.26f; + private static final float MAX_DARK_LUMA = 0.45f; + + private static final float MIN_LIGHT_LUMA = 0.55f; + private static final float TARGET_LIGHT_LUMA = 0.74f; + + private static final float MIN_NORMAL_LUMA = 0.3f; + private static final float TARGET_NORMAL_LUMA = 0.5f; + private static final float MAX_NORMAL_LUMA = 0.7f; + + private static final float TARGET_MUTED_SATURATION = 0.3f; + private static final float MAX_MUTED_SATURATION = 0.4f; + + private static final float TARGET_VIBRANT_SATURATION = 1f; + private static final float MIN_VIBRANT_SATURATION = 0.35f; + + private static final float WEIGHT_SATURATION = 3f; + private static final float WEIGHT_LUMA = 6f; + private static final float WEIGHT_POPULATION = 1f; + + private List mSwatches; + + private int mHighestPopulation; + + private Swatch mVibrantSwatch; + private Swatch mMutedSwatch; + private Swatch mDarkVibrantSwatch; + private Swatch mDarkMutedSwatch; + private Swatch mLightVibrantSwatch; + private Swatch mLightMutedSwatch; + + @Override + public void generate(final List swatches) { + mSwatches = swatches; + + mHighestPopulation = findMaxPopulation(); + + generateVariationColors(); + + // Now try and generate any missing colors + generateEmptySwatches(); + } + + @Override + public Swatch getVibrantSwatch() { + return mVibrantSwatch; + } + + @Override + public Swatch getLightVibrantSwatch() { + return mLightVibrantSwatch; + } + + @Override + public Swatch getDarkVibrantSwatch() { + return mDarkVibrantSwatch; + } + + @Override + public Swatch getMutedSwatch() { + return mMutedSwatch; + } + + @Override + public Swatch getLightMutedSwatch() { + return mLightMutedSwatch; + } + + @Override + public Swatch getDarkMutedSwatch() { + return mDarkMutedSwatch; + } + + private void generateVariationColors() { + mVibrantSwatch = findColorVariation(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA, + TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f); + + mLightVibrantSwatch = findColorVariation(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f, + TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f); + + mDarkVibrantSwatch = findColorVariation(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA, + TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f); + + mMutedSwatch = findColorVariation(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA, + TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION); + + mLightMutedSwatch = findColorVariation(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f, + TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION); + + mDarkMutedSwatch = findColorVariation(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA, + TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION); + } + + /** + * Try and generate any missing swatches from the swatches we did find. + */ + private void generateEmptySwatches() { + if (mVibrantSwatch == null) { + // If we do not have a vibrant color... + if (mDarkVibrantSwatch != null) { + // ...but we do have a dark vibrant, generate the value by modifying the luma + final float[] newHsl = copyHslValues(mDarkVibrantSwatch); + newHsl[2] = TARGET_NORMAL_LUMA; + mVibrantSwatch = new Swatch(ColorUtils.HSLToColor(newHsl), 0); + } + } + + if (mDarkVibrantSwatch == null) { + // If we do not have a dark vibrant color... + if (mVibrantSwatch != null) { + // ...but we do have a vibrant, generate the value by modifying the luma + final float[] newHsl = copyHslValues(mVibrantSwatch); + newHsl[2] = TARGET_DARK_LUMA; + mDarkVibrantSwatch = new Swatch(ColorUtils.HSLToColor(newHsl), 0); + } + } + } + + /** + * Find the {@link Palette.Swatch} with the highest population value and return the population. + */ + private int findMaxPopulation() { + int population = 0; + for (Swatch swatch : mSwatches) { + population = Math.max(population, swatch.getPopulation()); + } + return population; + } + + private Swatch findColorVariation(float targetLuma, float minLuma, float maxLuma, + float targetSaturation, float minSaturation, float maxSaturation) { + Swatch max = null; + float maxValue = 0f; + + for (Swatch swatch : mSwatches) { + final float sat = swatch.getHsl()[1]; + final float luma = swatch.getHsl()[2]; + + if (sat >= minSaturation && sat <= maxSaturation && + luma >= minLuma && luma <= maxLuma && + !isAlreadySelected(swatch)) { + float value = createComparisonValue(sat, targetSaturation, luma, targetLuma, + swatch.getPopulation(), mHighestPopulation); + if (max == null || value > maxValue) { + max = swatch; + maxValue = value; + } + } + } + + return max; + } + + /** + * @return true if we have already selected {@code swatch} + */ + private boolean isAlreadySelected(Swatch swatch) { + return mVibrantSwatch == swatch || mDarkVibrantSwatch == swatch || + mLightVibrantSwatch == swatch || mMutedSwatch == swatch || + mDarkMutedSwatch == swatch || mLightMutedSwatch == swatch; + } + + private static float createComparisonValue(float saturation, float targetSaturation, + float luma, float targetLuma, + int population, int maxPopulation) { + return createComparisonValue(saturation, targetSaturation, WEIGHT_SATURATION, + luma, targetLuma, WEIGHT_LUMA, + population, maxPopulation, WEIGHT_POPULATION); + } + + private static float createComparisonValue( + float saturation, float targetSaturation, float saturationWeight, + float luma, float targetLuma, float lumaWeight, + int population, int maxPopulation, float populationWeight) { + return weightedMean( + invertDiff(saturation, targetSaturation), saturationWeight, + invertDiff(luma, targetLuma), lumaWeight, + population / (float) maxPopulation, populationWeight + ); + } + + /** + * Copy a {@link Swatch}'s HSL values into a new float[]. + */ + private static float[] copyHslValues(Swatch color) { + final float[] newHsl = new float[3]; + System.arraycopy(color.getHsl(), 0, newHsl, 0, 3); + return newHsl; + } + + /** + * Returns a value in the range 0-1. 1 is returned when {@code value} equals the + * {@code targetValue} and then decreases as the absolute difference between {@code value} and + * {@code targetValue} increases. + * + * @param value the item's value + * @param targetValue the value which we desire + */ + private static float invertDiff(float value, float targetValue) { + return 1f - Math.abs(value - targetValue); + } + + private static float weightedMean(float... values) { + float sum = 0f; + float sumWeight = 0f; + + for (int i = 0; i < values.length; i += 2) { + float value = values[i]; + float weight = values[i + 1]; + + sum += (value * weight); + sumWeight += weight; + } + + return sum / sumWeight; + } +} diff --git a/core/java/com/android/internal/lineage/util/palette/Palette.java b/core/java/com/android/internal/lineage/util/palette/Palette.java new file mode 100644 index 000000000000..4d80ff7b5c86 --- /dev/null +++ b/core/java/com/android/internal/lineage/util/palette/Palette.java @@ -0,0 +1,740 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.lineage.util.palette; + +import android.graphics.Bitmap; +import android.graphics.Color; +import android.os.AsyncTask; +import android.annotation.ColorInt; +import android.annotation.Nullable; +import android.util.TimingLogger; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * A helper class to extract prominent colors from an image. + *

    + * A number of colors with different profiles are extracted from the image: + *

      + *
    • Vibrant
    • + *
    • Vibrant Dark
    • + *
    • Vibrant Light
    • + *
    • Muted
    • + *
    • Muted Dark
    • + *
    • Muted Light
    • + *
    + * These can be retrieved from the appropriate getter method. + * + *

    + * Instances are created with a {@link Builder} which supports several options to tweak the + * generated Palette. See that class' documentation for more information. + *

    + * Generation should always be completed on a background thread, ideally the one in + * which you load your image on. {@link Builder} supports both synchronous and asynchronous + * generation: + * + *

    + * // Synchronous
    + * Palette p = Palette.from(bitmap).generate();
    + *
    + * // Asynchronous
    + * Palette.from(bitmap).generate(new PaletteAsyncListener() {
    + *     public void onGenerated(Palette p) {
    + *         // Use generated instance
    + *     }
    + * });
    + * 
    + * + * @hide + */ +public final class Palette { + + /** + * Listener to be used with {@link #generateAsync(Bitmap, PaletteAsyncListener)} or + * {@link #generateAsync(Bitmap, int, PaletteAsyncListener)} + */ + public interface PaletteAsyncListener { + + /** + * Called when the {@link Palette} has been generated. + */ + void onGenerated(Palette palette); + } + + private static final int DEFAULT_RESIZE_BITMAP_MAX_DIMENSION = 192; + private static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16; + + private static final float MIN_CONTRAST_TITLE_TEXT = 3.0f; + private static final float MIN_CONTRAST_BODY_TEXT = 4.5f; + + private static final String LOG_TAG = "Palette"; + private static final boolean LOG_TIMINGS = false; + + /** + * Start generating a {@link Palette} with the returned {@link Builder} instance. + */ + public static Builder from(Bitmap bitmap) { + return new Builder(bitmap); + } + + /** + * Generate a {@link Palette} from the pre-generated list of {@link Palette.Swatch} swatches. + * This is useful for testing, or if you want to resurrect a {@link Palette} instance from a + * list of swatches. Will return null if the {@code swatches} is null. + */ + public static Palette from(List swatches) { + return new Builder(swatches).generate(); + } + + /** + * @deprecated Use {@link Builder} to generate the Palette. + */ + @Deprecated + public static Palette generate(Bitmap bitmap) { + return from(bitmap).generate(); + } + + /** + * @deprecated Use {@link Builder} to generate the Palette. + */ + @Deprecated + public static Palette generate(Bitmap bitmap, int numColors) { + return from(bitmap).maximumColorCount(numColors).generate(); + } + + /** + * @deprecated Use {@link Builder} to generate the Palette. + */ + @Deprecated + public static AsyncTask generateAsync( + Bitmap bitmap, PaletteAsyncListener listener) { + return from(bitmap).generate(listener); + } + + /** + * @deprecated Use {@link Builder} to generate the Palette. + */ + @Deprecated + public static AsyncTask generateAsync( + final Bitmap bitmap, final int numColors, final PaletteAsyncListener listener) { + return from(bitmap).maximumColorCount(numColors).generate(listener); + } + + private final List mSwatches; + private final Generator mGenerator; + + private Palette(List swatches, Generator generator) { + mSwatches = swatches; + mGenerator = generator; + } + + /** + * Returns all of the swatches which make up the palette. + */ + public List getSwatches() { + return Collections.unmodifiableList(mSwatches); + } + + /** + * Returns the most vibrant swatch in the palette. Might be null. + */ + @Nullable + public Swatch getVibrantSwatch() { + return mGenerator.getVibrantSwatch(); + } + + /** + * Returns a light and vibrant swatch from the palette. Might be null. + */ + @Nullable + public Swatch getLightVibrantSwatch() { + return mGenerator.getLightVibrantSwatch(); + } + + /** + * Returns a dark and vibrant swatch from the palette. Might be null. + */ + @Nullable + public Swatch getDarkVibrantSwatch() { + return mGenerator.getDarkVibrantSwatch(); + } + + /** + * Returns a muted swatch from the palette. Might be null. + */ + @Nullable + public Swatch getMutedSwatch() { + return mGenerator.getMutedSwatch(); + } + + /** + * Returns a muted and light swatch from the palette. Might be null. + */ + @Nullable + public Swatch getLightMutedSwatch() { + return mGenerator.getLightMutedSwatch(); + } + + /** + * Returns a muted and dark swatch from the palette. Might be null. + */ + @Nullable + public Swatch getDarkMutedSwatch() { + return mGenerator.getDarkMutedSwatch(); + } + + /** + * Returns the most vibrant color in the palette as an RGB packed int. + * + * @param defaultColor value to return if the swatch isn't available + */ + @ColorInt + public int getVibrantColor(@ColorInt int defaultColor) { + Swatch swatch = getVibrantSwatch(); + return swatch != null ? swatch.getRgb() : defaultColor; + } + + /** + * Returns a light and vibrant color from the palette as an RGB packed int. + * + * @param defaultColor value to return if the swatch isn't available + */ + @ColorInt + public int getLightVibrantColor(@ColorInt int defaultColor) { + Swatch swatch = getLightVibrantSwatch(); + return swatch != null ? swatch.getRgb() : defaultColor; + } + + /** + * Returns a dark and vibrant color from the palette as an RGB packed int. + * + * @param defaultColor value to return if the swatch isn't available + */ + @ColorInt + public int getDarkVibrantColor(@ColorInt int defaultColor) { + Swatch swatch = getDarkVibrantSwatch(); + return swatch != null ? swatch.getRgb() : defaultColor; + } + + /** + * Returns a muted color from the palette as an RGB packed int. + * + * @param defaultColor value to return if the swatch isn't available + */ + @ColorInt + public int getMutedColor(@ColorInt int defaultColor) { + Swatch swatch = getMutedSwatch(); + return swatch != null ? swatch.getRgb() : defaultColor; + } + + /** + * Returns a muted and light color from the palette as an RGB packed int. + * + * @param defaultColor value to return if the swatch isn't available + */ + @ColorInt + public int getLightMutedColor(@ColorInt int defaultColor) { + Swatch swatch = getLightMutedSwatch(); + return swatch != null ? swatch.getRgb() : defaultColor; + } + + /** + * Returns a muted and dark color from the palette as an RGB packed int. + * + * @param defaultColor value to return if the swatch isn't available + */ + @ColorInt + public int getDarkMutedColor(@ColorInt int defaultColor) { + Swatch swatch = getDarkMutedSwatch(); + return swatch != null ? swatch.getRgb() : defaultColor; + } + + /** + * Scale the bitmap down so that it's largest dimension is {@code targetMaxDimension}. + * If {@code bitmap} is smaller than this, then it is returned. + */ + private static Bitmap scaleBitmapDown(Bitmap bitmap, final int targetMaxDimension) { + final int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight()); + + if (maxDimension <= targetMaxDimension) { + // If the bitmap is small enough already, just return it + return bitmap; + } + + final float scaleRatio = targetMaxDimension / (float) maxDimension; + return Bitmap.createScaledBitmap(bitmap, + Math.round(bitmap.getWidth() * scaleRatio), + Math.round(bitmap.getHeight() * scaleRatio), + false); + } + + /** + * Represents a color swatch generated from an image's palette. The RGB color can be retrieved + * by calling {@link #getRgb()}. + */ + public static final class Swatch { + private final int mRed, mGreen, mBlue; + private final int mRgb; + private final int mPopulation; + + private boolean mGeneratedTextColors; + private int mTitleTextColor; + private int mBodyTextColor; + + private float[] mHsl; + + public Swatch(@ColorInt int color, int population) { + mRed = Color.red(color); + mGreen = Color.green(color); + mBlue = Color.blue(color); + mRgb = color; + mPopulation = population; + } + + Swatch(int red, int green, int blue, int population) { + mRed = red; + mGreen = green; + mBlue = blue; + mRgb = Color.rgb(red, green, blue); + mPopulation = population; + } + + /** + * @return this swatch's RGB color value + */ + @ColorInt + public int getRgb() { + return mRgb; + } + + /** + * Return this swatch's HSL values. + * hsv[0] is Hue [0 .. 360) + * hsv[1] is Saturation [0...1] + * hsv[2] is Lightness [0...1] + */ + public float[] getHsl() { + if (mHsl == null) { + mHsl = new float[3]; + ColorUtils.RGBToHSL(mRed, mGreen, mBlue, mHsl); + } + return mHsl; + } + + /** + * @return the number of pixels represented by this swatch + */ + public int getPopulation() { + return mPopulation; + } + + /** + * Returns an appropriate color to use for any 'title' text which is displayed over this + * {@link Swatch}'s color. This color is guaranteed to have sufficient contrast. + */ + @ColorInt + public int getTitleTextColor() { + ensureTextColorsGenerated(); + return mTitleTextColor; + } + + /** + * Returns an appropriate color to use for any 'body' text which is displayed over this + * {@link Swatch}'s color. This color is guaranteed to have sufficient contrast. + */ + @ColorInt + public int getBodyTextColor() { + ensureTextColorsGenerated(); + return mBodyTextColor; + } + + private void ensureTextColorsGenerated() { + if (!mGeneratedTextColors) { + // First check white, as most colors will be dark + final int lightBodyAlpha = ColorUtils.calculateMinimumAlpha( + Color.WHITE, mRgb, MIN_CONTRAST_BODY_TEXT); + final int lightTitleAlpha = ColorUtils.calculateMinimumAlpha( + Color.WHITE, mRgb, MIN_CONTRAST_TITLE_TEXT); + + if (lightBodyAlpha != -1 && lightTitleAlpha != -1) { + // If we found valid light values, use them and return + mBodyTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha); + mTitleTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha); + mGeneratedTextColors = true; + return; + } + + final int darkBodyAlpha = ColorUtils.calculateMinimumAlpha( + Color.BLACK, mRgb, MIN_CONTRAST_BODY_TEXT); + final int darkTitleAlpha = ColorUtils.calculateMinimumAlpha( + Color.BLACK, mRgb, MIN_CONTRAST_TITLE_TEXT); + + if (darkBodyAlpha != -1 && darkBodyAlpha != -1) { + // If we found valid dark values, use them and return + mBodyTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha); + mTitleTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha); + mGeneratedTextColors = true; + return; + } + + // If we reach here then we can not find title and body values which use the same + // lightness, we need to use mismatched values + mBodyTextColor = lightBodyAlpha != -1 + ? ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha) + : ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha); + mTitleTextColor = lightTitleAlpha != -1 + ? ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha) + : ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha); + mGeneratedTextColors = true; + } + } + + @Override + public String toString() { + return new StringBuilder(getClass().getSimpleName()) + .append(" [RGB: #").append(Integer.toHexString(getRgb())).append(']') + .append(" [HSL: ").append(Arrays.toString(getHsl())).append(']') + .append(" [Population: ").append(mPopulation).append(']') + .append(" [Title Text: #").append(Integer.toHexString(getTitleTextColor())) + .append(']') + .append(" [Body Text: #").append(Integer.toHexString(getBodyTextColor())) + .append(']').toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Swatch swatch = (Swatch) o; + return mPopulation == swatch.mPopulation && mRgb == swatch.mRgb; + } + + @Override + public int hashCode() { + return 31 * mRgb + mPopulation; + } + } + + /** + * Builder class for generating {@link Palette} instances. + */ + public static final class Builder { + private List mSwatches; + private Bitmap mBitmap; + private int mMaxColors = DEFAULT_CALCULATE_NUMBER_COLORS; + private int mResizeMaxDimension = DEFAULT_RESIZE_BITMAP_MAX_DIMENSION; + private final List mFilters = new ArrayList<>(); + + private Generator mGenerator; + + /** + * Construct a new {@link Builder} using a source {@link Bitmap} + */ + public Builder(Bitmap bitmap) { + this(); + if (bitmap == null || bitmap.isRecycled()) { + throw new IllegalArgumentException("Bitmap is not valid"); + } + mBitmap = bitmap; + } + + /** + * Construct a new {@link Builder} using a list of {@link Swatch} instances. + * Typically only used for testing. + */ + public Builder(List swatches) { + this(); + if (swatches == null || swatches.isEmpty()) { + throw new IllegalArgumentException("List of Swatches is not valid"); + } + mSwatches = swatches; + } + + private Builder() { + mFilters.add(DEFAULT_FILTER); + } + + /** + * Set the {@link Generator} to use when generating the {@link Palette}. If this is called + * with {@code null} then the default generator will be used. + */ + Builder generator(Generator generator) { + mGenerator = generator; + return this; + } + + /** + * Set the maximum number of colors to use in the quantization step when using a + * {@link android.graphics.Bitmap} as the source. + *

    + * Good values for depend on the source image type. For landscapes, good values are in + * the range 10-16. For images which are largely made up of people's faces then this + * value should be increased to ~24. + */ + public Builder maximumColorCount(int colors) { + mMaxColors = colors; + return this; + } + + /** + * Set the resize value when using a {@link android.graphics.Bitmap} as the source. + * If the bitmap's largest dimension is greater than the value specified, then the bitmap + * will be resized so that it's largest dimension matches {@code maxDimension}. If the + * bitmap is smaller or equal, the original is used as-is. + *

    + * This value has a large effect on the processing time. The larger the resized image is, + * the greater time it will take to generate the palette. The smaller the image is, the + * more detail is lost in the resulting image and thus less precision for color selection. + */ + public Builder resizeBitmapSize(int maxDimension) { + mResizeMaxDimension = maxDimension; + return this; + } + + /** + * Clear all added filters. This includes any default filters added automatically by + * {@link Palette}. + */ + public Builder clearFilters() { + mFilters.clear(); + return this; + } + + /** + * Add a filter to be able to have fine grained controlled over the colors which are + * allowed in the resulting palette. + * + * @param filter filter to add. + */ + public Builder addFilter(Filter filter) { + if (filter != null) { + mFilters.add(filter); + } + return this; + } + + /** + * Generate and return the {@link Palette} synchronously. + */ + public Palette generate() { + final TimingLogger logger = LOG_TIMINGS + ? new TimingLogger(LOG_TAG, "Generation") + : null; + + List swatches; + + if (mBitmap != null) { + // We have a Bitmap so we need to quantization to reduce the number of colors + + if (mResizeMaxDimension <= 0) { + throw new IllegalArgumentException( + "Minimum dimension size for resizing should should be >= 1"); + } + + // First we'll scale down the bitmap so it's largest dimension is as specified + final Bitmap scaledBitmap = scaleBitmapDown(mBitmap, mResizeMaxDimension); + + if (logger != null) { + logger.addSplit("Processed Bitmap"); + } + + // Now generate a quantizer from the Bitmap + final int width = scaledBitmap.getWidth(); + final int height = scaledBitmap.getHeight(); + final int[] pixels = new int[width * height]; + scaledBitmap.getPixels(pixels, 0, width, 0, 0, width, height); + + final ColorCutQuantizer quantizer = new ColorCutQuantizer(pixels, mMaxColors, + mFilters.isEmpty() ? null : mFilters.toArray(new Filter[0])); + + // If created a new bitmap, recycle it + if (scaledBitmap != mBitmap) { + scaledBitmap.recycle(); + } + swatches = quantizer.getQuantizedColors(); + + if (logger != null) { + logger.addSplit("Color quantization completed"); + } + } else { + // Else we're using the provided swatches + swatches = mSwatches; + } + + // If we haven't been provided with a generator, use the default + if (mGenerator == null) { + mGenerator = new DefaultGenerator(); + } + + // Now call let the Generator do it's thing + mGenerator.generate(swatches); + + if (logger != null) { + logger.addSplit("Generator.generate() completed"); + } + + // Now create a Palette instance + Palette p = new Palette(swatches, mGenerator); + + if (logger != null) { + logger.addSplit("Created Palette"); + logger.dumpToLog(); + } + + return p; + } + + /** + * Generate the {@link Palette} asynchronously. The provided listener's + * {@link PaletteAsyncListener#onGenerated} method will be called with the palette when + * generated. + */ + public AsyncTask generate(final PaletteAsyncListener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener can not be null"); + } + + AsyncTask task = new AsyncTask() { + @Override + protected Palette doInBackground(Bitmap... params) { + return generate(); + } + + @Override + protected void onPostExecute(Palette colorExtractor) { + listener.onGenerated(colorExtractor); + } + }; + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mBitmap); + return task; + } + } + + static abstract class Generator { + + /** + * This method will be called with the {@link Palette.Swatch} that represent an image. + * You should process this list so that you have appropriate values when the other methods in + * class are called. + *

    + * This method will probably be called on a background thread. + */ + public abstract void generate(List swatches); + + /** + * Return the most vibrant {@link Palette.Swatch} + */ + public Palette.Swatch getVibrantSwatch() { + return null; + } + + /** + * Return a light and vibrant {@link Palette.Swatch} + */ + public Palette.Swatch getLightVibrantSwatch() { + return null; + } + + /** + * Return a dark and vibrant {@link Palette.Swatch} + */ + public Palette.Swatch getDarkVibrantSwatch() { + return null; + } + + /** + * Return a muted {@link Palette.Swatch} + */ + public Palette.Swatch getMutedSwatch() { + return null; + } + + /** + * Return a muted and light {@link Palette.Swatch} + */ + public Palette.Swatch getLightMutedSwatch() { + return null; + } + + /** + * Return a muted and dark {@link Palette.Swatch} + */ + public Palette.Swatch getDarkMutedSwatch() { + return null; + } + } + + /** + * A Filter provides a mechanism for exercising fine-grained control over which colors + * are valid within a resulting {@link Palette}. + */ + public interface Filter { + /** + * Hook to allow clients to be able filter colors from resulting palette. + * + * @param rgb the color in RGB888. + * @param hsl HSL representation of the color. + * + * @return true if the color is allowed, false if not. + * + * @see Builder#addFilter(Filter) + */ + boolean isAllowed(int rgb, float[] hsl); + } + + /** + * The default filter. + */ + private static final Filter DEFAULT_FILTER = new Filter() { + private static final float BLACK_MAX_LIGHTNESS = 0.05f; + private static final float WHITE_MIN_LIGHTNESS = 0.95f; + + @Override + public boolean isAllowed(int rgb, float[] hsl) { + return !isWhite(hsl) && !isBlack(hsl) && !isNearRedILine(hsl); + } + + /** + * @return true if the color represents a color which is close to black. + */ + private boolean isBlack(float[] hslColor) { + return hslColor[2] <= BLACK_MAX_LIGHTNESS; + } + + /** + * @return true if the color represents a color which is close to white. + */ + private boolean isWhite(float[] hslColor) { + return hslColor[2] >= WHITE_MIN_LIGHTNESS; + } + + /** + * @return true if the color lies close to the red side of the I line. + */ + private boolean isNearRedILine(float[] hslColor) { + return hslColor[0] >= 10f && hslColor[0] <= 37f && hslColor[1] <= 0.82f; + } + }; +} diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java index fef5e83cecca..c80dbc66cd6d 100644 --- a/core/java/com/android/internal/notification/SystemNotificationChannels.java +++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java @@ -105,7 +105,7 @@ public static void createAll(Context context) { final NotificationChannel developerImportant = new NotificationChannel( DEVELOPER_IMPORTANT, context.getString(R.string.notification_channel_developer_important), - NotificationManager.IMPORTANCE_HIGH); + NotificationManager.IMPORTANCE_MIN); developer.setBlockable(true); channelsList.add(developerImportant); @@ -164,6 +164,7 @@ public static void createAll(Context context) { USB, context.getString(R.string.notification_channel_usb), NotificationManager.IMPORTANCE_MIN); + usb.setBlockable(true); channelsList.add(usb); NotificationChannel foregroundChannel = new NotificationChannel( diff --git a/core/java/com/android/internal/os/DeviceKeyHandler.java b/core/java/com/android/internal/os/DeviceKeyHandler.java new file mode 100644 index 000000000000..8902337f3ebb --- /dev/null +++ b/core/java/com/android/internal/os/DeviceKeyHandler.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012 The CyanogenMod 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.os; + +import android.view.KeyEvent; + +public interface DeviceKeyHandler { + + /** + * Invoked when an unknown key was detected by the system, letting the device handle + * this special keys prior to pass the key to the active app. + * + * @param event The key event to be handled + * @return null if event is consumed, KeyEvent to be handled otherwise + */ + public KeyEvent handleKeyEvent(KeyEvent event); +} diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java index 21e0dc59db01..eef1f2254d7b 100644 --- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java +++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java @@ -18,6 +18,7 @@ import static com.android.internal.os.KernelCpuProcStringReader.asLongs; import android.annotation.Nullable; +import android.os.Build; import android.os.StrictMode; import android.util.IntArray; import android.util.Slog; @@ -485,7 +486,11 @@ void readDeltaImpl(@Nullable Callback cb, boolean forceRead) { CharBuffer buf; while ((buf = iter.nextLine()) != null) { if (asLongs(buf, mBuffer) != mBuffer.length) { - Slog.wtf(mTag, "Invalid line: " + buf.toString()); + if (Build.IS_ENG) { + Slog.wtf(mTag, "Invalid line: " + buf.toString()); + } else { + Slog.w(mTag, "Invalid line: " + buf.toString()); + } continue; } processUidDelta(cb); diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java index 39aadfb24b0c..3dd3f9b5e68e 100644 --- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java +++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java @@ -72,10 +72,15 @@ public AconfigFlags() { (Process.myUid() == Process.SYSTEM_UID) ? DeviceProtos.parsedFlagsProtoPaths() : Arrays.asList(DeviceProtos.PATHS); for (String fileName : defaultFlagProtoFiles) { - try (var inputStream = new FileInputStream(fileName)) { - loadAconfigDefaultValues(inputStream.readAllBytes()); - } catch (IOException e) { - Slog.e(LOG_TAG, "Failed to read Aconfig values from " + fileName, e); + File f = new File(fileName); + if (f.isFile() && f.canRead()) { + try (var inputStream = new FileInputStream(fileName)) { + loadAconfigDefaultValues(inputStream.readAllBytes()); + } catch (IOException e) { + Slog.e(LOG_TAG, "Failed to read Aconfig values from " + fileName, e); + } + } else { + Slog.d(LOG_TAG, "No Aconfig flags at " + fileName); } } if (Process.myUid() == Process.SYSTEM_UID) { diff --git a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java index b7e68bacd143..45b414e6972c 100644 --- a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java +++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java @@ -77,7 +77,12 @@ public void register() { DeviceConfig.NAMESPACE_SYSTEMUI, runnable -> mMainHandler.post(runnable), mOnPropertiesChangedListener); + + r.registerContentObserver( + Settings.System.getUriFor(Settings.System.LOCK_GESTURE_STATUS), + false, this, UserHandle.USER_ALL); }); + } /** diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index e0529b339d4a..25b52c330d87 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -34,6 +34,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; @@ -2696,6 +2697,16 @@ protected ViewGroup generateLayout(DecorView decor) { params.layoutInDisplayCutoutMode = mode; } + if (ActivityManager.isSystemReady()) { + try { + String packageName = context.getBasePackageName(); + if (ActivityManager.getService().shouldForceCutoutFullscreen(packageName)){ + params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + } + } catch (RemoteException e) { + } + } + if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.HONEYCOMB) { if (a.getBoolean( diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index c834dde56bfe..e989707d807b 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -390,4 +390,9 @@ oneway interface IStatusBar * @param displayId the id of the current display. */ void moveFocusedTaskToDesktop(int displayId); + + /** + * Toggles flashlight of the device + */ + void toggleCameraFlash(); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index ff08dd27225f..53b64bcb1279 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -102,6 +102,7 @@ interface IStatusBarService */ void shutdown(); void reboot(boolean safeMode); + void advancedReboot(String mode); /** just restarts android without rebooting device. Used for some feature flags. */ void restart(); @@ -229,4 +230,19 @@ interface IStatusBarService /** Shows rear display educational dialog */ void showRearDisplayDialog(int currentBaseState); + + /** + * Starts the default assistant app. + */ + void startAssist(in Bundle args); + + /** + * Toggles flashlight of the device + */ + void toggleCameraFlash(); + + /** + * Toggle recent apps. + */ + void toggleRecentApps(); } diff --git a/core/java/com/android/internal/util/MkrBinUtils.java b/core/java/com/android/internal/util/MkrBinUtils.java new file mode 100644 index 000000000000..3f0422730a55 --- /dev/null +++ b/core/java/com/android/internal/util/MkrBinUtils.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2018 Potato Open Sauce Project + * Copyright (C) 2021 Jyotiraditya Panda + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import android.os.Handler; +import android.os.HandlerThread; + +import java.io.OutputStreamWriter; +import java.net.URL; + +import javax.net.ssl.HttpsURLConnection; + +/** + * Helper functions for uploading to MkrBin (https://bin.mkr.pw/). + */ +public final class MkrBinUtils { + + private static final String binUrl = "https://bin.mkr.pw"; + private static Handler mHandler; + + /** + * Uploads {@code content} to MkrBin + * + * @param content the content to upload to MkrBin + * @param callback the callback to call on success / failure + */ + public static void upload(String content, UploadResultCallback callback) { + getHandler().post(() -> { + try { + URL url = new URL(binUrl); + HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); + urlConnection.setRequestProperty("Content-Type", "text/plain"); + urlConnection.setInstanceFollowRedirects(false); + urlConnection.setDoOutput(true); + + try (OutputStreamWriter writer = new OutputStreamWriter(urlConnection.getOutputStream())) { + writer.write(content); + writer.flush(); + } + + String urlPath = ""; + if (urlConnection.getResponseCode() == HttpsURLConnection.HTTP_MOVED_TEMP) { + urlPath = urlConnection.getHeaderField("Location"); + } + + if (!urlPath.isEmpty()) { + callback.onSuccess(binUrl + urlPath); + } else { + String msg = "Failed to upload to MkrBin: No id retrieved"; + callback.onFail(msg, new Exception(msg)); + } + urlConnection.disconnect(); + } catch (Exception e) { + String msg = "Failed to upload to MkrBin"; + callback.onFail(msg, e); + } + }); + } + + private static Handler getHandler() { + if (mHandler == null) { + HandlerThread mkrBinThread = new HandlerThread("MkrBinThread"); + if (!mkrBinThread.isAlive()) { + mkrBinThread.start(); + } + mHandler = new Handler(mkrBinThread.getLooper()); + } + return mHandler; + } + + public interface UploadResultCallback { + void onSuccess(String url); + + void onFail(String message, Exception e); + } +} diff --git a/core/java/com/android/internal/util/PropImitationHooks.java b/core/java/com/android/internal/util/PropImitationHooks.java new file mode 100644 index 000000000000..c01bb29192a9 --- /dev/null +++ b/core/java/com/android/internal/util/PropImitationHooks.java @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2022 Paranoid Android + * (C) 2023 ArrowOS + * (C) 2023 The LibreMobileOS Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import android.app.ActivityTaskManager; +import android.app.Application; +import android.app.TaskStackListener; +import android.content.ComponentName; +import android.content.Context; +import android.content.res.Resources; +import android.os.Build; +import android.os.Binder; +import android.os.Process; +import android.os.SystemProperties; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.R; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; + +/** + * @hide + */ +public class PropImitationHooks { + + private static final String TAG = "PropImitationHooks"; + private static final boolean DEBUG = SystemProperties.getBoolean("debug.pihooks.log", false); + + private static final String PACKAGE_AIWALLPAPERS = "com.google.android.apps.aiwallpapers"; + private static final String PACKAGE_ARCORE = "com.google.ar.core"; + private static final String PACKAGE_ASI = "com.google.android.as"; + private static final String PACKAGE_ASSISTANT = "com.google.android.apps.googleassistant"; + private static final String PACKAGE_EMOJIWALLPAPER = "com.google.android.apps.emojiwallpaper"; + + private static final String PACKAGE_FINSKY = "com.android.vending"; + private static final String PACKAGE_GMS = "com.google.android.gms"; + private static final String PACKAGE_GPHOTOS = "com.google.android.apps.photos"; + private static final String PACKAGE_NETFLIX = "com.netflix.mediaclient"; + + private static final String PACKAGE_NEXUSLAUNCHER = "com.google.android.apps.nexuslauncher"; + private static final String PACKAGE_PIXELTHEMES = "com.google.android.apps.customization.pixel"; + private static final String PACKAGE_PIXELWALLPAPER = "com.google.android.apps.wallpaper.pixel"; + private static final String PACKAGE_LIVEWALLPAPER = "com.google.pixel.livewallpaper"; + private static final String PACKAGE_SUBSCRIPTION_RED = "com.google.android.apps.subscriptions.red"; + private static final String PACKAGE_VELVET = "com.google.android.googlequicksearchbox"; + private static final String PACKAGE_WALLPAPER = "com.google.android.apps.wallpaper"; + private static final String PACKAGE_WALLPAPEREFFECTS = "com.google.android.wallpaper.effects"; + + private static final String PROCESS_GMS_GAPPS = PACKAGE_GMS + ".gapps"; + private static final String PROCESS_GMS_GSERVICE = PACKAGE_GMS + ".gservice"; + private static final String PROCESS_GMS_LEARNING = PACKAGE_GMS + ".learning"; + private static final String PROCESS_GMS_PERSISTENT = PACKAGE_GMS + ".persistent"; + private static final String PROCESS_GMS_SEARCH = PACKAGE_GMS + ".search"; + private static final String PROCESS_GMS_UNSTABLE = PACKAGE_GMS + ".unstable"; + private static final String PROCESS_GMS_UPDATE = PACKAGE_GMS + ".update"; + + private static final String PROP_SECURITY_PATCH = "persist.sys.pihooks.security_patch"; + private static final String PROP_FIRST_API_LEVEL = "persist.sys.pihooks.first_api_level"; + + private static final ComponentName GMS_ADD_ACCOUNT_ACTIVITY = ComponentName.unflattenFromString( + "com.google.android.gms/.auth.uiflows.minutemaid.MinuteMaidActivity"); + + private static final Boolean sDisableGmsProps = SystemProperties.getBoolean( + "persist.sys.pihooks.disable.gms_props", false); + + private static final Boolean sDisableKeyAttestationBlock = SystemProperties.getBoolean( + "persist.sys.pihooks.disable.gms_key_attestation_block", false); + + private static final Map sPixelNineProps = Map.of( + "PRODUCT", "caiman", + "DEVICE", "caiman", + "HARDWARE", "caiman", + "MANUFACTURER", "Google", + "BRAND", "google", + "MODEL", "Pixel 9 Pro", + "ID", "AP4A.241205.013.C1", + "FINGERPRINT", "google/caiman/caiman:15/AP4A.241205.013.C1/12657666:user/release-keys" + ); + + private static final Map sPixelFiveProps = Map.of( + "PRODUCT", "barbet", + "DEVICE", "barbet", + "HARDWARE", "barbet", + "MANUFACTURER", "Google", + "BRAND", "google", + "MODEL", "Pixel 5a", + "ID", "AP2A.240805.005.S4", + "FINGERPRINT", "google/barbet/barbet:14/AP2A.240805.005.S4/12281092:user/release-keys" + ); + + private static final Map sPixelTabletProps = Map.of( + "PRODUCT", "tangorpro", + "DEVICE", "tangorpro", + "HARDWARE", "tangorpro", + "MANUFACTURER", "Google", + "BRAND", "google", + "MODEL", "Pixel Tablet", + "ID", "AP4A.241205.013", + "FINGERPRINT", "google/tangorpro/tangorpro:15/AP4A.241205.013/12621605:user/release-keys" + ); + + private static final Map sPixelXLProps = Map.of( + "PRODUCT", "marlin", + "DEVICE", "marlin", + "HARDWARE", "marlin", + "MANUFACTURER", "Google", + "BRAND", "google", + "MODEL", "Pixel XL", + "ID", "QP1A.191005.007.A3", + "FINGERPRINT", "google/marlin/marlin:10/QP1A.191005.007.A3/5972272:user/release-keys" + ); + + private static final Set sNexusFeatures = Set.of( + "NEXUS_PRELOAD", + "nexus_preload", + "GOOGLE_BUILD", + "GOOGLE_EXPERIENCE", + "PIXEL_EXPERIENCE" + ); + + private static final Set sPixelFeatures = Set.of( + "PIXEL_2017_EXPERIENCE", + "PIXEL_2017_PRELOAD", + "PIXEL_2018_EXPERIENCE", + "PIXEL_2018_PRELOAD", + "PIXEL_2019_EXPERIENCE", + "PIXEL_2019_MIDYEAR_EXPERIENCE", + "PIXEL_2019_MIDYEAR_PRELOAD", + "PIXEL_2019_PRELOAD", + "PIXEL_2020_EXPERIENCE", + "PIXEL_2020_MIDYEAR_EXPERIENCE", + "PIXEL_2021_MIDYEAR_EXPERIENCE" + ); + + private static final Set sTensorFeatures = Set.of( + "PIXEL_2021_EXPERIENCE", + "PIXEL_2022_EXPERIENCE", + "PIXEL_2022_MIDYEAR_EXPERIENCE", + "PIXEL_2023_EXPERIENCE", + "PIXEL_2023_MIDYEAR_EXPERIENCE", + "PIXEL_2024_EXPERIENCE", + "PIXEL_2024_MIDYEAR_EXPERIENCE" + ); + + private static volatile String[] sCertifiedProps; + private static volatile String sStockFp, sNetflixModel; + + private static volatile String sProcessName; + private static volatile boolean sIsGms, sIsFinsky, sIsPhotos; + + public static void setProps(Context context) { + final String packageName = context.getPackageName(); + final String processName = Application.getProcessName(); + + if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(processName)) { + Log.e(TAG, "Null package or process name"); + return; + } + + final Resources res = context.getResources(); + if (res == null) { + Log.e(TAG, "Null resources"); + return; + } + + sCertifiedProps = res.getStringArray(R.array.config_certifiedBuildProperties); + sStockFp = res.getString(R.string.config_stockFingerprint); + sNetflixModel = res.getString(R.string.config_netflixSpoofModel); + + sProcessName = processName; + sIsGms = packageName.equals(PACKAGE_GMS) && processName.equals(PROCESS_GMS_UNSTABLE); + sIsFinsky = packageName.equals(PACKAGE_FINSKY); + sIsPhotos = packageName.equals(PACKAGE_GPHOTOS); + + /* Set certified properties for GMSCore + * Set stock fingerprint for ARCore + * Set Pixel 8 Pro for Google, ASI and GMS device configurator + * Set Pixel XL for Google Photos + * Set custom model for Netflix + */ + + switch (processName) { + case PROCESS_GMS_UNSTABLE: + dlog("Setting certified props for: " + packageName + " process: " + processName); + setCertifiedPropsForGms(); + return; + case PROCESS_GMS_PERSISTENT: + case PROCESS_GMS_GAPPS: + case PROCESS_GMS_GSERVICE: + case PROCESS_GMS_LEARNING: + case PROCESS_GMS_SEARCH: + case PROCESS_GMS_UPDATE: + dlog("Spoofing Pixel 5a for: " + packageName + " process: " + processName); + setProps(sPixelFiveProps); + return; + } + + if (!sStockFp.isEmpty() && packageName.equals(PACKAGE_ARCORE)) { + dlog("Setting stock fingerprint for: " + packageName); + setPropValue("FINGERPRINT", sStockFp); + return; + } + + switch (packageName) { + case PACKAGE_AIWALLPAPERS: + case PACKAGE_ASSISTANT: + case PACKAGE_ASI: + case PACKAGE_EMOJIWALLPAPER: + case PACKAGE_GMS: + case PACKAGE_LIVEWALLPAPER: + case PACKAGE_NEXUSLAUNCHER: + case PACKAGE_PIXELTHEMES: + case PACKAGE_PIXELWALLPAPER: + case PACKAGE_SUBSCRIPTION_RED: + case PACKAGE_VELVET: + case PACKAGE_WALLPAPER: + case PACKAGE_WALLPAPEREFFECTS: + if (SystemProperties.get("ro.build.characteristics").equals("tablet")) { + dlog("Spoofing Pixel Tablet for: " + packageName + " process: " + processName); + setProps(sPixelTabletProps); + } else { + dlog("Spoofing Pixel 9 Pro for: " + packageName + " process: " + processName); + setProps(sPixelNineProps); + } + return; + case PACKAGE_GPHOTOS: + dlog("Spoofing Pixel XL for Google Photos"); + setProps(sPixelXLProps); + return; + case PACKAGE_NETFLIX: + if (!sNetflixModel.isEmpty()) { + dlog("Setting model to " + sNetflixModel + " for Netflix"); + setPropValue("MODEL", sNetflixModel); + } + return; + } + } + + private static void setProps(Map props) { + props.forEach(PropImitationHooks::setPropValue); + } + + private static void setPropValue(String key, String value) { + try { + dlog("Setting prop " + key + " to " + value.toString()); + Class clazz = Build.class; + if (key.startsWith("VERSION.")) { + clazz = Build.VERSION.class; + key = key.substring(8); + } + Field field = clazz.getDeclaredField(key); + field.setAccessible(true); + // Cast the value to int if it's an integer field, otherwise string. + field.set(null, field.getType().equals(Integer.TYPE) ? Integer.parseInt(value) : value); + field.setAccessible(false); + } catch (Exception e) { + Log.e(TAG, "Failed to set prop " + key, e); + } + } + + private static void setCertifiedPropsForGms() { + if (sDisableGmsProps) { + dlog("GMS prop imitation is disabled by user"); + setSystemProperty(PROP_SECURITY_PATCH, Build.VERSION.SECURITY_PATCH); + setSystemProperty(PROP_FIRST_API_LEVEL, + Integer.toString(Build.VERSION.DEVICE_INITIAL_SDK_INT)); + return; + } + + if (sCertifiedProps.length == 0) { + dlog("Certified props are not set"); + return; + } + final boolean was = isGmsAddAccountActivityOnTop(); + final TaskStackListener taskStackListener = new TaskStackListener() { + @Override + public void onTaskStackChanged() { + final boolean is = isGmsAddAccountActivityOnTop(); + if (is ^ was) { + dlog("GmsAddAccountActivityOnTop is:" + is + " was:" + was + + ", killing myself!"); // process will restart automatically later + Process.killProcess(Process.myPid()); + } + } + }; + if (!was) { + dlog("Spoofing build for GMS"); + setCertifiedProps(); + } else { + dlog("Skip spoofing build for GMS, because GmsAddAccountActivityOnTop"); + } + try { + ActivityTaskManager.getService().registerTaskStackListener(taskStackListener); + } catch (Exception e) { + Log.e(TAG, "Failed to register task stack listener!", e); + } + } + + private static void setCertifiedProps() { + for (String entry : sCertifiedProps) { + // Each entry must be of the format FIELD:value + final String[] fieldAndProp = entry.split(":", 2); + if (fieldAndProp.length != 2) { + Log.e(TAG, "Invalid entry in certified props: " + entry); + continue; + } + setPropValue(fieldAndProp[0], fieldAndProp[1]); + } + setSystemProperty(PROP_SECURITY_PATCH, Build.VERSION.SECURITY_PATCH); + setSystemProperty(PROP_FIRST_API_LEVEL, + Integer.toString(Build.VERSION.DEVICE_INITIAL_SDK_INT)); + } + + private static void setSystemProperty(String name, String value) { + try { + SystemProperties.set(name, value); + dlog("Set system prop " + name + "=" + value); + } catch (Exception e) { + Log.e(TAG, "Failed to set system prop " + name + "=" + value, e); + } + } + + private static boolean isGmsAddAccountActivityOnTop() { + try { + final ActivityTaskManager.RootTaskInfo focusedTask = + ActivityTaskManager.getService().getFocusedRootTaskInfo(); + return focusedTask != null && focusedTask.topActivity != null + && focusedTask.topActivity.equals(GMS_ADD_ACCOUNT_ACTIVITY); + } catch (Exception e) { + Log.e(TAG, "Unable to get top activity!", e); + } + return false; + } + + public static boolean shouldBypassTaskPermission(Context context) { + if (sDisableGmsProps) { + return false; + } + + // GMS doesn't have MANAGE_ACTIVITY_TASKS permission + final int callingUid = Binder.getCallingUid(); + final int gmsUid; + try { + gmsUid = context.getPackageManager().getApplicationInfo(PACKAGE_GMS, 0).uid; + dlog("shouldBypassTaskPermission: gmsUid:" + gmsUid + " callingUid:" + callingUid); + } catch (Exception e) { + Log.e(TAG, "shouldBypassTaskPermission: unable to get gms uid", e); + return false; + } + return gmsUid == callingUid; + } + + private static boolean isCallerSafetyNet() { + return sIsGms && Arrays.stream(Thread.currentThread().getStackTrace()) + .anyMatch(elem -> elem.getClassName().contains("DroidGuard")); + } + + public static void onEngineGetCertificateChain() { + if (sDisableKeyAttestationBlock) { + dlog("Key attestation blocking is disabled by user"); + return; + } + + // Check stack for SafetyNet or Play Integrity + if (isCallerSafetyNet() || sIsFinsky) { + dlog("Blocked key attestation sIsGms=" + sIsGms + " sIsFinsky=" + sIsFinsky); + throw new UnsupportedOperationException(); + } + } + + public static boolean hasSystemFeature(String name, boolean has) { + if (sIsPhotos) { + if (has && (sPixelFeatures.stream().anyMatch(name::contains) + || sTensorFeatures.stream().anyMatch(name::contains))) { + dlog("Blocked system feature " + name + " for Google Photos"); + has = false; + } else if (!has && sNexusFeatures.stream().anyMatch(name::contains)) { + dlog("Enabled system feature " + name + " for Google Photos"); + has = true; + } + } + return has; + } + + public static void dlog(String msg) { + if (DEBUG) Log.d(TAG, "[" + sProcessName + "] " + msg); + } +} diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java index c21a43e807a9..0c3ac405c209 100644 --- a/core/java/com/android/internal/util/ScreenshotHelper.java +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -20,6 +20,7 @@ import android.os.UserHandle; import android.util.Log; import android.view.WindowManager.ScreenshotSource; +import android.view.WindowManager.ScreenshotType; import com.android.internal.annotations.VisibleForTesting; @@ -68,8 +69,25 @@ public ScreenshotHelper(Context context) { */ public void takeScreenshot(@ScreenshotSource int source, @NonNull Handler handler, @Nullable Consumer completionConsumer) { + takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, source, handler, completionConsumer); + } + + /** + * Request a screenshot be taken. + *

    + * Convenience method for taking a full screenshot with provided source. + * + * @param type type of screenshot, defined by {@link ScreenshotType} + * @param source source of the screenshot request, defined by {@link + * ScreenshotSource} + * @param handler used to process messages received from the screenshot service + * @param completionConsumer receives the URI of the captured screenshot, once saved or + * null if no screenshot was saved + */ + public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source, + @NonNull Handler handler, @Nullable Consumer completionConsumer) { ScreenshotRequest request = - new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, source).build(); + new ScreenshotRequest.Builder(type, source).build(); takeScreenshot(request, handler, completionConsumer); } diff --git a/core/java/com/android/internal/util/ScreenshotRequest.java b/core/java/com/android/internal/util/ScreenshotRequest.java index c8b7defb5276..dfef895642d3 100644 --- a/core/java/com/android/internal/util/ScreenshotRequest.java +++ b/core/java/com/android/internal/util/ScreenshotRequest.java @@ -20,6 +20,7 @@ import static android.os.UserHandle.USER_NULL; import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE; +import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION; import android.annotation.NonNull; import android.content.ComponentName; @@ -173,7 +174,8 @@ public static class Builder { public Builder( @WindowManager.ScreenshotType int type, @WindowManager.ScreenshotSource int source) { - if (type != TAKE_SCREENSHOT_FULLSCREEN && type != TAKE_SCREENSHOT_PROVIDED_IMAGE) { + if (type != TAKE_SCREENSHOT_FULLSCREEN && type != TAKE_SCREENSHOT_PROVIDED_IMAGE + && type != TAKE_SCREENSHOT_SELECTED_REGION) { throw new IllegalArgumentException("Invalid screenshot type requested!"); } mType = type; @@ -187,6 +189,9 @@ public ScreenshotRequest build() { if (mType == TAKE_SCREENSHOT_FULLSCREEN && mBitmap != null) { Log.w(TAG, "Bitmap provided, but request is fullscreen. Bitmap will be ignored."); } + if (mType == TAKE_SCREENSHOT_SELECTED_REGION && mBitmap != null) { + Log.w(TAG, "Bitmap provided, but request is partial. Bitmap will be ignored."); + } if (mType == TAKE_SCREENSHOT_PROVIDED_IMAGE && mBitmap == null) { throw new IllegalStateException( "Request is PROVIDED_IMAGE, but no bitmap is provided!"); diff --git a/core/java/com/android/internal/util/custom/DeviceConfigUtils.java b/core/java/com/android/internal/util/custom/DeviceConfigUtils.java new file mode 100644 index 000000000000..ad168382d1c1 --- /dev/null +++ b/core/java/com/android/internal/util/custom/DeviceConfigUtils.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 The Pixel Experience Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util.custom; + +import android.content.res.Resources; +import android.provider.Settings; +import android.util.Log; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import com.android.internal.util.ArrayUtils; + +public class DeviceConfigUtils { + + private static final String TAG = DeviceConfigUtils.class.getSimpleName(); + + private static final boolean DEBUG = false; + + private static String[] getDeviceConfigsOverride() { + String[] globalDeviceConfigs = + Resources.getSystem().getStringArray(com.android.internal.R.array.global_device_configs_override); + String[] deviceConfigs = + Resources.getSystem().getStringArray(com.android.internal.R.array.device_configs_override); + String[] allDeviceConfigs = Arrays.copyOf(globalDeviceConfigs, globalDeviceConfigs.length + deviceConfigs.length); + System.arraycopy(deviceConfigs, 0, allDeviceConfigs, globalDeviceConfigs.length, deviceConfigs.length); + return allDeviceConfigs; + } + + public static boolean shouldDenyDeviceConfigControl(String namespace, String property) { + if (DEBUG) Log.d(TAG, "shouldAllowDeviceConfigControl, namespace=" + namespace + ", property=" + property); + for (String p : getDeviceConfigsOverride()) { + String[] kv = p.split("="); + String fullKey = kv[0]; + String[] nsKey = fullKey.split("/"); + if (nsKey[0] == namespace && nsKey[1] == property){ + if (DEBUG) Log.d(TAG, "shouldAllowDeviceConfigControl, deny, namespace=" + namespace + ", property=" + property); + return true; + } + } + if (DEBUG) Log.d(TAG, "shouldAllowDeviceConfigControl, allow, namespace=" + namespace + ", property=" + property); + return false; + } + + public static void setDefaultProperties(String filterNamespace, String filterProperty) { + if (DEBUG) Log.d(TAG, "setDefaultProperties"); + for (String p : getDeviceConfigsOverride()) { + String[] kv = p.split("="); + String fullKey = kv[0]; + String[] nsKey = fullKey.split("/"); + + String namespace = nsKey[0]; + String key = nsKey[1]; + + if (filterNamespace != null && filterNamespace == namespace){ + continue; + } + + if (filterProperty != null && filterProperty == key){ + continue; + } + + String value = ""; + if (kv.length > 1) { + value = kv[1]; + } + Settings.Config.putString(namespace, key, value, false); + } + } +} diff --git a/core/java/com/android/internal/util/custom/FileUtils.java b/core/java/com/android/internal/util/custom/FileUtils.java new file mode 100644 index 000000000000..9929ff8d96a4 --- /dev/null +++ b/core/java/com/android/internal/util/custom/FileUtils.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util.custom; + +import android.annotation.Nullable; +import android.util.Log; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; + +public final class FileUtils { + private static final String TAG = "FileUtils"; + + private FileUtils() { + // This class is not supposed to be instantiated + } + + /** + * Reads the first line of text from the given file. + * Reference {@link BufferedReader#readLine()} for clarification on what a line is + * + * @return the read line contents, or null on failure + */ + @Nullable + public static String readOneLine(String fileName) { + String line = null; + BufferedReader reader = null; + + try { + reader = new BufferedReader(new FileReader(fileName), 512); + line = reader.readLine(); + } catch (FileNotFoundException e) { + Log.w(TAG, "No such file " + fileName + " for reading", e); + } catch (IOException e) { + Log.w(TAG, "Could not read from file " + fileName, e); + } finally { + try { + if (reader != null) { + reader.close(); + } + } catch (IOException e) { + // Ignored, not much we can do anyway + } + } + + return line; + } + + /** + * Writes the given value into the given file + * + * @return true on success, false on failure + */ + public static boolean writeLine(String fileName, String value) { + BufferedWriter writer = null; + + try { + writer = new BufferedWriter(new FileWriter(fileName)); + writer.write(value); + } catch (FileNotFoundException e) { + Log.w(TAG, "No such file " + fileName + " for writing", e); + return false; + } catch (IOException e) { + Log.e(TAG, "Could not write to file " + fileName, e); + return false; + } finally { + try { + if (writer != null) { + writer.close(); + } + } catch (IOException e) { + // Ignored, not much we can do anyway + } + } + + return true; + } + + /** + * Checks whether the given file exists + * + * @return true if exists, false if not + */ + public static boolean fileExists(String fileName) { + final File file = new File(fileName); + return file.exists(); + } + + /** + * Checks whether the given file is readable + * + * @return true if readable, false if not + */ + public static boolean isFileReadable(String fileName) { + final File file = new File(fileName); + return file.exists() && file.canRead(); + } + + /** + * Checks whether the given file is writable + * + * @return true if writable, false if not + */ + public static boolean isFileWritable(String fileName) { + final File file = new File(fileName); + return file.exists() && file.canWrite(); + } + + /** + * Deletes an existing file + * + * @return true if the delete was successful, false if not + */ + public static boolean delete(String fileName) { + final File file = new File(fileName); + boolean ok = false; + try { + ok = file.delete(); + } catch (SecurityException e) { + Log.w(TAG, "SecurityException trying to delete " + fileName, e); + } + return ok; + } + + /** + * Renames an existing file + * + * @return true if the rename was successful, false if not + */ + public static boolean rename(String srcPath, String dstPath) { + final File srcFile = new File(srcPath); + final File dstFile = new File(dstPath); + boolean ok = false; + try { + ok = srcFile.renameTo(dstFile); + } catch (SecurityException e) { + Log.w(TAG, "SecurityException trying to rename " + srcPath + " to " + dstPath, e); + } catch (NullPointerException e) { + Log.e(TAG, "NullPointerException trying to rename " + srcPath + " to " + dstPath, e); + } + return ok; + } +} diff --git a/core/java/com/android/internal/util/pixelage/cutout/CutoutFullscreenController.java b/core/java/com/android/internal/util/pixelage/cutout/CutoutFullscreenController.java new file mode 100644 index 000000000000..907610d0d27f --- /dev/null +++ b/core/java/com/android/internal/util/pixelage/cutout/CutoutFullscreenController.java @@ -0,0 +1,118 @@ +/** + * Copyright (C) 2018 The LineageOS project + * 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.util.pixelage.cutout; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.text.TextUtils; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import android.provider.Settings; + +public class CutoutFullscreenController { + private Set mApps = new HashSet<>(); + private Context mContext; + + private final boolean isAvailable; + + public CutoutFullscreenController(Context context) { + mContext = context; + final Resources resources = mContext.getResources(); + + final String displayCutout = resources.getString(com.android.internal.R.string.config_mainBuiltInDisplayCutout); + isAvailable = !TextUtils.isEmpty(displayCutout); + + if (!isAvailable) { + return; + } + + SettingsObserver observer = new SettingsObserver( + new Handler(Looper.getMainLooper())); + observer.observe(); + } + + public boolean isSupported() { + return isAvailable; + } + + public boolean shouldForceCutoutFullscreen(String packageName) { + return isSupported() && mApps.contains(packageName); + } + + public Set getApps() { + return mApps; + } + + public void addApp(String packageName) { + mApps.add(packageName); + Settings.System.putString(mContext.getContentResolver(), + Settings.System.FORCE_FULLSCREEN_CUTOUT_APPS, String.join(",", mApps)); + } + + public void removeApp(String packageName) { + mApps.remove(packageName); + Settings.System.putString(mContext.getContentResolver(), + Settings.System.FORCE_FULLSCREEN_CUTOUT_APPS, String.join(",", mApps)); + } + + public void setApps(Set apps) { + mApps = apps; + } + + class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + void observe() { + ContentResolver resolver = mContext.getContentResolver(); + + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.FORCE_FULLSCREEN_CUTOUT_APPS), false, this, + UserHandle.USER_ALL); + + update(); + } + + @Override + public void onChange(boolean selfChange) { + update(); + } + + public void update() { + ContentResolver resolver = mContext.getContentResolver(); + + String apps = Settings.System.getStringForUser(resolver, + Settings.System.FORCE_FULLSCREEN_CUTOUT_APPS, + UserHandle.USER_CURRENT); + if (apps != null) { + setApps(new HashSet<>(Arrays.asList(apps.split(",")))); + } else { + setApps(new HashSet<>()); + } + } + } +} diff --git a/core/java/com/android/internal/view/RotationPolicy.java b/core/java/com/android/internal/view/RotationPolicy.java index 6e45796df053..5fbdf5973976 100644 --- a/core/java/com/android/internal/view/RotationPolicy.java +++ b/core/java/com/android/internal/view/RotationPolicy.java @@ -26,6 +26,8 @@ import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; +import android.sysprop.SurfaceFlingerProperties; +import android.sysprop.SurfaceFlingerProperties.primary_display_orientation_values; import android.util.DisplayMetrics; import android.util.Log; import android.view.IWindowManager; @@ -34,6 +36,8 @@ import com.android.internal.R; +import java.util.Optional; + /** * Provides helper functions for configuring the display rotation policy. */ @@ -41,7 +45,7 @@ public final class RotationPolicy { private static final String TAG = "RotationPolicy"; private static final int CURRENT_ROTATION = -1; - public static final int NATURAL_ROTATION = Surface.ROTATION_0; + private static int sNaturalRotation = -1; private RotationPolicy() { } @@ -72,7 +76,7 @@ public static boolean isRotationSupported(Context context) { * otherwise Configuration.ORIENTATION_UNDEFINED if any orientation is lockable. */ public static int getRotationLockOrientation(Context context) { - if (areAllRotationsAllowed(context)) { + if (isCurrentRotationAllowed(context)) { return Configuration.ORIENTATION_UNDEFINED; } final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); @@ -106,9 +110,9 @@ public static boolean isRotationLocked(Context context) { * Enables or disables rotation lock from the system UI toggle. */ public static void setRotationLock(Context context, final boolean enabled, String caller) { - final int rotation = areAllRotationsAllowed(context) + final int rotation = isCurrentRotationAllowed(context) || useCurrentRotationOnRotationLockChange(context) ? CURRENT_ROTATION - : NATURAL_ROTATION; + : getNaturalRotation(); setRotationLockAtAngle(context, enabled, rotation, caller); } @@ -135,11 +139,43 @@ public static void setRotationLockForAccessibility(Context context, final boolea Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, enabled ? 1 : 0, UserHandle.USER_CURRENT); - setRotationLock(enabled, NATURAL_ROTATION, caller); + setRotationLock(enabled, getNaturalRotation(), caller); } - private static boolean areAllRotationsAllowed(Context context) { - return context.getResources().getBoolean(R.bool.config_allowAllRotations); + public static boolean isRotationAllowed(int rotation, + int userRotationAngles, boolean allowAllRotations) { + if (userRotationAngles < 0) { + // Not set by user so use these defaults + userRotationAngles = allowAllRotations ? + (1 | 2 | 4 | 8) : // All angles + (1 | 2 | 8); // All except 180 + } + switch (rotation) { + case Surface.ROTATION_0: + return (userRotationAngles & 1) != 0; + case Surface.ROTATION_90: + return (userRotationAngles & 2) != 0; + case Surface.ROTATION_180: + return (userRotationAngles & 4) != 0; + case Surface.ROTATION_270: + return (userRotationAngles & 8) != 0; + } + return false; + } + + private static boolean isCurrentRotationAllowed(Context context) { + int userRotationAngles = Settings.System.getIntForUser(context.getContentResolver(), + Settings.System.ACCELEROMETER_ROTATION_ANGLES, -1, UserHandle.USER_CURRENT); + boolean allowAllRotations = context.getResources().getBoolean( + com.android.internal.R.bool.config_allowAllRotations); + final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + try { + return isRotationAllowed(wm.getDefaultDisplayRotation(), userRotationAngles, + allowAllRotations); + } catch (RemoteException exc) { + Log.w(TAG, "Unable to getWindowManagerService.getDefaultDisplayRotation()"); + } + return false; } private static boolean useCurrentRotationOnRotationLockChange(Context context) { @@ -196,6 +232,35 @@ public static void unregisterRotationPolicyListener(Context context, context.getContentResolver().unregisterContentObserver(listener.mObserver); } + public static int getNaturalRotation() { + if (sNaturalRotation == -1) { + sNaturalRotation = getNaturalRotationConfig(); + } + return sNaturalRotation; + } + + private static int getNaturalRotationConfig() { + primary_display_orientation_values orientation = + primary_display_orientation_values.ORIENTATION_0; + Optional primaryDisplayOrientation = + SurfaceFlingerProperties.primary_display_orientation(); + if (primaryDisplayOrientation.isPresent()) { + orientation = primaryDisplayOrientation.get(); + } + + if (orientation == primary_display_orientation_values.ORIENTATION_90) { + return Surface.ROTATION_90; + } + if (orientation == primary_display_orientation_values.ORIENTATION_180) { + return Surface.ROTATION_180; + } + if (orientation == primary_display_orientation_values.ORIENTATION_270) { + return Surface.ROTATION_270; + } + + return Surface.ROTATION_0; + } + /** * Listener that is invoked whenever a change occurs that might affect the rotation policy. */ @@ -209,4 +274,4 @@ public void onChange(boolean selfChange, Uri uri) { public abstract void onChange(); } -} \ No newline at end of file +} diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index 511c6802677e..a19dc669f619 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -110,4 +110,5 @@ interface ILockSettings { boolean isWeakEscrowTokenValid(long handle, in byte[] token, int userId); void unlockUserKeyIfUnsecured(int userId); boolean writeRepairModeCredential(int userId); + byte getLockPatternSize(int userId); } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 19c6f51ff9a7..aac510277703 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -103,6 +103,11 @@ public class LockPatternUtils { */ public static final int MIN_LOCK_PASSWORD_SIZE = 4; + /* + * The default size of the pattern lockscreen. Ex: 3x3 + */ + public static final byte PATTERN_SIZE_DEFAULT = 3; + /** * The minimum number of dots the user must include in a wrong pattern attempt for it to be * counted. @@ -122,7 +127,7 @@ public class LockPatternUtils { public static final int PIN_LENGTH_UNAVAILABLE = -1; // This is the minimum pin length at which auto confirmation is supported - public static final int MIN_AUTO_PIN_REQUIREMENT_LENGTH = 6; + public static final int MIN_AUTO_PIN_REQUIREMENT_LENGTH = 4; /** * Header used for the encryption and decryption of the device credential for @@ -974,16 +979,18 @@ private boolean isCredentialSharableWithParent(int userHandle) { * @param bytes The pattern serialized with {@link #patternToByteArray} * @return The pattern. */ - public static List byteArrayToPattern(byte[] bytes) { + public static List byteArrayToPattern(byte[] bytes, byte gridSize) { if (bytes == null) { return null; } List result = Lists.newArrayList(); + LockPatternView.Cell.updateSize(gridSize); + for (int i = 0; i < bytes.length; i++) { byte b = (byte) (bytes[i] - '1'); - result.add(LockPatternView.Cell.of(b / 3, b % 3)); + result.add(LockPatternView.Cell.of(b / gridSize, b % gridSize, gridSize)); } return result; } @@ -993,7 +1000,7 @@ public static List byteArrayToPattern(byte[] bytes) { * @param pattern The pattern. * @return The pattern in byte array form. */ - public static byte[] patternToByteArray(List pattern) { + public static byte[] patternToByteArray(List pattern, byte gridSize) { if (pattern == null) { return new byte[0]; } @@ -1002,7 +1009,7 @@ public static byte[] patternToByteArray(List pattern) { byte[] res = new byte[patternSize]; for (int i = 0; i < patternSize; i++) { LockPatternView.Cell cell = pattern.get(i); - res[i] = (byte) (cell.getRow() * 3 + cell.getColumn() + '1'); + res[i] = (byte) (cell.getRow() * gridSize + cell.getColumn() + '1'); } return res; } @@ -1072,6 +1079,40 @@ public final static void invalidateCredentialTypeCache() { return mCredentialTypeCache.query(userHandle); } + /** + * @return the pattern lockscreen size + */ + public byte getLockPatternSize(int userId) { + long size = getLong(Settings.Secure.LOCK_PATTERN_SIZE, -1, userId); + if (size > 0 && size < 128) { + return (byte) size; + } + return LockPatternUtils.PATTERN_SIZE_DEFAULT; + } + + /** + * Set the pattern lockscreen size + */ + public void setLockPatternSize(long size, int userId) { + setLong(Settings.Secure.LOCK_PATTERN_SIZE, size, userId); + } + + public void setVisibleDotsEnabled(boolean enabled, int userId) { + setBoolean(Settings.Secure.LOCK_DOTS_VISIBLE, enabled, userId); + } + + public boolean isVisibleDotsEnabled(int userId) { + return getBoolean(Settings.Secure.LOCK_DOTS_VISIBLE, true, userId); + } + + public void setShowErrorPath(boolean enabled, int userId) { + setBoolean(Settings.Secure.LOCK_SHOW_ERROR_PATH, enabled, userId); + } + + public boolean isShowErrorPath(int userId) { + return getBoolean(Settings.Secure.LOCK_SHOW_ERROR_PATH, true, userId); + } + /** * @param userId the user for which to report the value * @return Whether the lock screen is secured. diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 0ec55f958f38..d57039bcae27 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -58,13 +58,14 @@ import com.android.internal.R; import com.android.internal.graphics.ColorUtils; +import com.android.internal.widget.LockPatternUtils; import java.util.ArrayList; import java.util.List; /** * Displays and detects the user's unlock attempt, which is a drag of a finger - * across 9 regions of the screen. + * across regions of the screen. * * Is also capable of displaying a static pattern in "in progress", "wrong" or * "correct" states. @@ -82,7 +83,7 @@ public class LockPatternView extends View { private static final int DOT_RADIUS_DECREASE_DURATION_MILLIS = 192; private static final int ALPHA_MAX_VALUE = 255; private static final float MIN_DOT_HIT_FACTOR = 0.2f; - private final CellState[][] mCellStates; + private CellState[][] mCellStates; private static final int CELL_ACTIVATE = 0; private static final int CELL_DEACTIVATE = 1; @@ -110,6 +111,8 @@ public class LockPatternView extends View { */ private static final int MILLIS_PER_CIRCLE_ANIMATING = 700; + private byte mPatternSize = LockPatternUtils.PATTERN_SIZE_DEFAULT; + /** * This can be used to avoid updating the display for very small motions or noisy panels. * It didn't seem to have much impact on the devices tested, so currently set to 0. @@ -122,7 +125,7 @@ public class LockPatternView extends View { private OnPatternListener mOnPatternListener; private ExternalHapticsPlayer mExternalHapticsPlayer; @UnsupportedAppUsage - private final ArrayList mPattern = new ArrayList(9); + private ArrayList mPattern = new ArrayList(mPatternSize * mPatternSize); /** * Lookup table for the circles of the pattern we are currently drawing. @@ -130,7 +133,7 @@ public class LockPatternView extends View { * in which case we use this to hold the cells we are drawing for the in * progress animation. */ - private final boolean[][] mPatternDrawLookup = new boolean[3][3]; + private boolean[][] mPatternDrawLookup = new boolean[mPatternSize][mPatternSize]; /** * the in progress point: @@ -141,7 +144,7 @@ public class LockPatternView extends View { private float mInProgressY = -1; private long mAnimatingPeriodStart; - private long[] mLineFadeStart = new long[9]; + private long[] mLineFadeStart = new long[mPatternSize * mPatternSize]; @UnsupportedAppUsage private DisplayMode mPatternDisplayMode = DisplayMode.Correct; @@ -151,6 +154,8 @@ public class LockPatternView extends View { @UnsupportedAppUsage private boolean mPatternInProgress = false; private boolean mFadePattern = true; + private boolean mVisibleDots = true; + private boolean mShowErrorPath = true; private boolean mFadeClear = false; private int mFadeAnimationAlpha = ALPHA_MAX_VALUE; @@ -186,8 +191,10 @@ public class LockPatternView extends View { private Drawable mNotSelectedDrawable; private boolean mUseLockPatternDrawable; + private LockPatternUtils mLockPatternUtils; + /** - * Represents a cell in the 3 X 3 matrix of the unlock pattern view. + * Represents a cell in the matrix of the unlock pattern view. */ public static final class Cell { @UnsupportedAppUsage @@ -195,25 +202,18 @@ public static final class Cell { @UnsupportedAppUsage final int column; - // keep # objects limited to 9 - private static final Cell[][] sCells = createCells(); - - private static Cell[][] createCells() { - Cell[][] res = new Cell[3][3]; - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - res[i][j] = new Cell(i, j); - } - } - return res; + static Cell[][] sCells; + static { + updateSize(LockPatternUtils.PATTERN_SIZE_DEFAULT); } /** * @param row The row of the cell. * @param column The column of the cell. + * @param size The size of the cell. */ - private Cell(int row, int column) { - checkRange(row, column); + private Cell(int row, int column, byte size) { + checkRange(row, column, size); this.row = row; this.column = column; } @@ -226,20 +226,28 @@ public int getColumn() { return column; } - public static Cell of(int row, int column) { - checkRange(row, column); + public static Cell of(int row, int column, byte size) { + checkRange(row, column, size); return sCells[row][column]; } - private static void checkRange(int row, int column) { - if (row < 0 || row > 2) { - throw new IllegalArgumentException("row must be in range 0-2"); - } - if (column < 0 || column > 2) { - throw new IllegalArgumentException("column must be in range 0-2"); + public static void updateSize(byte size) { + sCells = new Cell[size][size]; + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + sCells[i][j] = new Cell(i, j, size); + } } } + private static void checkRange(int row, int column, byte size) { + if (row < 0 || row > size - 1) { + throw new IllegalArgumentException("row must be in range 0-" + (size - 1)); + } + if (column < 0 || column > size - 1) { + throw new IllegalArgumentException("column must be in range 0-" + (size - 1)); + } + } @Override public String toString() { return "(row=" + row + ",clmn=" + column + ")"; @@ -314,8 +322,9 @@ public static interface OnPatternListener { /** * A pattern was detected from the user. * @param pattern The pattern. + * @param patternSize The pattern size. */ - void onPatternDetected(List pattern); + void onPatternDetected(List pattern, byte patternSize); } /** An external haptics player for pattern updates. */ @@ -398,9 +407,9 @@ public LockPatternView(Context context, AttributeSet attrs) { mPaint.setAntiAlias(true); mPaint.setDither(true); - mCellStates = new CellState[3][3]; - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { + mCellStates = new CellState[mPatternSize][mPatternSize]; + for (int i = 0; i < mPatternSize; i++) { + for (int j = 0; j < mPatternSize; j++) { mCellStates[i][j] = new CellState(); mCellStates[i][j].radius = mDotSize/2; mCellStates[i][j].row = i; @@ -439,6 +448,13 @@ public boolean isInStealthMode() { return mInStealthMode; } + /** + * @return the current pattern lockscreen size. + */ + public byte getLockPatternSize() { + return mPatternSize; + } + /** * Set whether the view is in stealth mode. If true, there will be no * visible feedback as the user enters the pattern. @@ -450,6 +466,22 @@ public void setInStealthMode(boolean inStealthMode) { mInStealthMode = inStealthMode; } + public void setVisibleDots(boolean visibleDots) { + mVisibleDots = visibleDots; + } + + public boolean isVisibleDots() { + return mVisibleDots; + } + + public void setShowErrorPath(boolean showErrorPath) { + mShowErrorPath = showErrorPath; + } + + public boolean isShowErrorPath() { + return mShowErrorPath; + } + /** * Set whether the pattern should fade as it's being drawn. If * true, each segment of the pattern fades over time. @@ -458,6 +490,36 @@ public void setFadePattern(boolean fadePattern) { mFadePattern = fadePattern; } + /** + * Set the pattern size of the lockscreen + * + * @param size The pattern size. + */ + public void setLockPatternSize(byte size) { + mPatternSize = size; + Cell.updateSize(size); + mCellStates = new CellState[mPatternSize][mPatternSize]; + for (int i = 0; i < mPatternSize; i++) { + for (int j = 0; j < mPatternSize; j++) { + mCellStates[i][j] = new CellState(); + mCellStates[i][j].radius = mDotSize / 2; + mCellStates[i][j].row = i; + mCellStates[i][j].col = j; + } + } + mPattern = new ArrayList(size * size); + mLineFadeStart = new long[size * size]; + mPatternDrawLookup = new boolean[size][size]; + } + + /** + * Set the LockPatternUtil instance used to encode a pattern to a string + * @param utils The instance. + */ + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + /** * Set the call back for pattern detection. * @param onPatternListener The call back. @@ -644,7 +706,7 @@ private void notifyPatternStarted() { private void notifyPatternDetected() { sendAccessEvent(R.string.lockscreen_access_pattern_detected); if (mOnPatternListener != null) { - mOnPatternListener.onPatternDetected(mPattern); + mOnPatternListener.onPatternDetected(mPattern, mPatternSize); } } @@ -723,10 +785,10 @@ public boolean isEmpty() { * the next attempt. */ private void clearPatternDrawLookup() { - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { + for (int i = 0; i < mPatternSize; i++) { + for (int j = 0; j < mPatternSize; j++) { mPatternDrawLookup[i][j] = false; - mLineFadeStart[i+j*3] = 0; + mLineFadeStart[i * mPatternSize + j] = 0; } } } @@ -751,11 +813,11 @@ public void enableInput() { @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { final int width = w - mPaddingLeft - mPaddingRight; - mSquareWidth = width / 3.0f; + mSquareWidth = width / (float) mPatternSize; if (DEBUG_A11Y) Log.v(TAG, "onSizeChanged(" + w + "," + h + ")"); final int height = h - mPaddingTop - mPaddingBottom; - mSquareHeight = height / 3.0f; + mSquareHeight = height / (float) mPatternSize; mExploreByTouchHelper.invalidateRoot(); mDotHitMaxRadius = Math.min(mSquareHeight / 2, mSquareWidth / 2); mDotHitRadius = mDotHitMaxRadius * mDotHitFactor; @@ -818,7 +880,6 @@ private Cell detectAndAddHit(float x, float y) { if (cell != null) { // check for gaps in existing pattern - Cell fillInGapCell = null; final ArrayList pattern = mPattern; Cell lastCell = null; if (!pattern.isEmpty()) { @@ -829,33 +890,19 @@ private Cell detectAndAddHit(float x, float y) { int fillInRow = lastCell.row; int fillInColumn = lastCell.column; - if (Math.abs(dRow) == 2 && Math.abs(dColumn) != 1) { - fillInRow = lastCell.row + ((dRow > 0) ? 1 : -1); - } - - if (Math.abs(dColumn) == 2 && Math.abs(dRow) != 1) { - fillInColumn = lastCell.column + ((dColumn > 0) ? 1 : -1); - } - - fillInGapCell = Cell.of(fillInRow, fillInColumn); - } - - if (fillInGapCell != null && - !mPatternDrawLookup[fillInGapCell.row][fillInGapCell.column]) { - addCellToPattern(fillInGapCell); - if (mKeepDotActivated) { - if (mFadePattern) { - startCellDeactivatedAnimation(fillInGapCell, /* fillInGap= */ true); - } else { - startCellActivatedAnimation(fillInGapCell); + if (dRow == 0 || dColumn == 0 || Math.abs(dRow) == Math.abs(dColumn)) { + while (true) { + fillInRow += Integer.signum(dRow); + fillInColumn += Integer.signum(dColumn); + if (fillInRow == cell.row && fillInColumn == cell.column) break; + Cell fillInGapCell = Cell.of(fillInRow, fillInColumn, mPatternSize); + if (!mPatternDrawLookup[fillInGapCell.row][fillInGapCell.column]) { + addCellToPattern(fillInGapCell); + } } } } - if (mKeepDotActivated && lastCell != null) { - startCellDeactivatedAnimation(lastCell, /* fillInGap= */ false); - } - addCellToPattern(cell); performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); @@ -1086,8 +1133,8 @@ private Cell checkForNewHit(float x, float y) { /** Helper method to find which cell a point maps to. */ @Nullable private Cell detectCellHit(float x, float y) { - for (int row = 0; row < 3; row++) { - for (int column = 0; column < 3; column++) { + for (int row = 0; row < mPatternSize; row++) { + for (int column = 0; column < mPatternSize; column++) { float centerY = getCenterYForRow(row); float centerX = getCenterXForColumn(column); float hitRadiusSquared; @@ -1106,7 +1153,7 @@ private Cell detectCellHit(float x, float y) { if ((x - centerX) * (x - centerX) + (y - centerY) * (y - centerY) < hitRadiusSquared) { - return Cell.of(row, column); + return Cell.of(row, column, mPatternSize); } } } @@ -1259,11 +1306,6 @@ private void handleActionUp() { cancelLineAnimations(); } notifyPatternDetected(); - // Also clear pattern if fading is enabled - if (mFadePattern) { - clearPatternDrawLookup(); - mPatternDisplayMode = DisplayMode.Correct; - } invalidate(); } if (PROFILE_DRAWING) { @@ -1280,8 +1322,8 @@ private void deactivateLastCell() { } private void cancelLineAnimations() { - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { + for (int i = 0; i < mPatternSize; i++) { + for (int j = 0; j < mPatternSize; j++) { CellState state = mCellStates[i][j]; if (state.activationAnimator != null) { state.activationAnimator.cancel(); @@ -1403,7 +1445,9 @@ protected void onDraw(Canvas canvas) { // TODO: the path should be created and cached every time we hit-detect a cell // only the last segment of the path should be computed here // draw the path of the pattern (unless we are in stealth mode) - final boolean drawPath = !mInStealthMode; + final boolean drawWrongPath = mPatternDisplayMode == DisplayMode.Wrong && mShowErrorPath; + final boolean drawPath = (!mInStealthMode && mPatternDisplayMode != DisplayMode.Wrong) + || drawWrongPath; if (drawPath && !mFadeClear) { mPathPaint.setColor(getCurrentColor(true /* partOfPattern */)); @@ -1472,24 +1516,26 @@ protected void onDraw(Canvas canvas) { } // draw the circles - for (int i = 0; i < 3; i++) { - float centerY = getCenterYForRow(i); - for (int j = 0; j < 3; j++) { - CellState cellState = mCellStates[i][j]; - float centerX = getCenterXForColumn(j); - float translationY = cellState.translationY; - - if (mUseLockPatternDrawable) { - drawCellDrawable(canvas, i, j, cellState.radius, drawLookup[i][j]); - } else { - if (isHardwareAccelerated() && cellState.hwAnimating) { - RecordingCanvas recordingCanvas = (RecordingCanvas) canvas; - recordingCanvas.drawCircle(cellState.hwCenterX, cellState.hwCenterY, - cellState.hwRadius, cellState.hwPaint); + if (mVisibleDots) { + for (int i = 0; i < mPatternSize; i++) { + float centerY = getCenterYForRow(i); + for (int j = 0; j < mPatternSize; j++) { + CellState cellState = mCellStates[i][j]; + float centerX = getCenterXForColumn(j); + float translationY = cellState.translationY; + + if (mUseLockPatternDrawable) { + drawCellDrawable(canvas, i, j, cellState.radius, drawLookup[i][j]); } else { - drawCircle(canvas, (int) centerX, (int) centerY + translationY, - cellState.radius, drawLookup[i][j], cellState.alpha, - cellState.activationAnimationProgress); + if (isHardwareAccelerated() && cellState.hwAnimating) { + RecordingCanvas recordingCanvas = (RecordingCanvas) canvas; + recordingCanvas.drawCircle(cellState.hwCenterX, cellState.hwCenterY, + cellState.hwRadius, cellState.hwPaint); + } else { + drawCircle(canvas, (int) centerX, (int) centerY + translationY, + cellState.radius, drawLookup[i][j], cellState.alpha, + cellState.activationAnimationProgress); + } } } } @@ -1499,7 +1545,8 @@ protected void onDraw(Canvas canvas) { private void drawLineSegment(Canvas canvas, float startX, float startY, float endX, float endY, long lineFadeStart, long elapsedRealtime) { float fadeAwayProgress; - if (mFadePattern) { + final boolean drawWrongPath = mPatternDisplayMode == DisplayMode.Wrong && mShowErrorPath; + if (mFadePattern && !drawWrongPath) { if (elapsedRealtime - lineFadeStart >= mLineFadeOutAnimationDelayMs + mLineFadeOutAnimationDurationMs) { // Time for this segment animation is out so we don't need to draw it. @@ -1571,7 +1618,10 @@ private int getDotColor() { } private int getCurrentColor(boolean partOfPattern) { - if (!partOfPattern || mInStealthMode || mPatternInProgress) { + if (!partOfPattern + || (mInStealthMode && mPatternDisplayMode != DisplayMode.Wrong) + || (mPatternDisplayMode == DisplayMode.Wrong && !mShowErrorPath) + || mPatternInProgress) { // unselected circle return mRegularColor; } else if (mPatternDisplayMode == DisplayMode.Wrong) { @@ -1590,7 +1640,8 @@ private int getCurrentColor(boolean partOfPattern) { */ private void drawCircle(Canvas canvas, float centerX, float centerY, float radius, boolean partOfPattern, float alpha, float activationAnimationProgress) { - if (mFadePattern && !mInStealthMode) { + final boolean drawWrongPath = mPatternDisplayMode == DisplayMode.Wrong && mShowErrorPath; + if (mFadePattern && !mInStealthMode && !drawWrongPath) { int resultColor = ColorUtils.blendARGB(mDotColor, mDotActivatedColor, /* ratio= */ activationAnimationProgress); mPaint.setColor(resultColor); @@ -1630,12 +1681,13 @@ private void drawCellDrawable(Canvas canvas, int i, int j, float radius, @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); - byte[] patternBytes = LockPatternUtils.patternToByteArray(mPattern); + byte[] patternBytes = LockPatternUtils.patternToByteArray(mPattern, mPatternSize); String patternString = patternBytes != null ? new String(patternBytes) : null; return new SavedState(superState, patternString, mPatternDisplayMode.ordinal(), - mInputEnabled, mInStealthMode); + mPatternSize, mInputEnabled, mInStealthMode, + mVisibleDots, mShowErrorPath); } @Override @@ -1644,10 +1696,14 @@ protected void onRestoreInstanceState(Parcelable state) { super.onRestoreInstanceState(ss.getSuperState()); setPattern( DisplayMode.Correct, - LockPatternUtils.byteArrayToPattern(ss.getSerializedPattern().getBytes())); + LockPatternUtils.byteArrayToPattern(ss.getSerializedPattern().getBytes(), + ss.getPatternSize())); mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()]; + mPatternSize = ss.getPatternSize(); mInputEnabled = ss.isInputEnabled(); mInStealthMode = ss.isInStealthMode(); + mVisibleDots = ss.isVisibleDots(); + mShowErrorPath = ss.isShowErrorPath(); } @Override @@ -1664,20 +1720,27 @@ private static class SavedState extends BaseSavedState { private final String mSerializedPattern; private final int mDisplayMode; + private final byte mPatternSize; private final boolean mInputEnabled; private final boolean mInStealthMode; + private final boolean mVisibleDots; + private final boolean mShowErrorPath; /** * Constructor called from {@link LockPatternView#onSaveInstanceState()} */ @UnsupportedAppUsage private SavedState(Parcelable superState, String serializedPattern, int displayMode, - boolean inputEnabled, boolean inStealthMode) { + byte patternSize, boolean inputEnabled, boolean inStealthMode, + boolean visibleDots, boolean showErrorPath) { super(superState); mSerializedPattern = serializedPattern; mDisplayMode = displayMode; + mPatternSize = patternSize; mInputEnabled = inputEnabled; mInStealthMode = inStealthMode; + mVisibleDots = visibleDots; + mShowErrorPath = showErrorPath; } /** @@ -1688,8 +1751,11 @@ private SavedState(Parcel in) { super(in); mSerializedPattern = in.readString(); mDisplayMode = in.readInt(); + mPatternSize = (byte) in.readByte(); mInputEnabled = (Boolean) in.readValue(null); mInStealthMode = (Boolean) in.readValue(null); + mVisibleDots = (Boolean) in.readValue(null); + mShowErrorPath = (Boolean) in.readValue(null); } public String getSerializedPattern() { @@ -1700,6 +1766,10 @@ public int getDisplayMode() { return mDisplayMode; } + public byte getPatternSize() { + return mPatternSize; + } + public boolean isInputEnabled() { return mInputEnabled; } @@ -1708,13 +1778,24 @@ public boolean isInStealthMode() { return mInStealthMode; } + public boolean isVisibleDots() { + return mVisibleDots; + } + + public boolean isShowErrorPath() { + return mShowErrorPath; + } + @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeString(mSerializedPattern); dest.writeInt(mDisplayMode); + dest.writeByte(mPatternSize); dest.writeValue(mInputEnabled); dest.writeValue(mInStealthMode); + dest.writeValue(mVisibleDots); + dest.writeValue(mShowErrorPath); } @SuppressWarnings({ "unused", "hiding" }) // Found using reflection diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java index 54b9a225f944..4bfc9048db4f 100644 --- a/core/java/com/android/internal/widget/LockscreenCredential.java +++ b/core/java/com/android/internal/widget/LockscreenCredential.java @@ -121,9 +121,11 @@ public static LockscreenCredential createNone() { /** * Creates a LockscreenCredential object representing the given pattern. */ - public static LockscreenCredential createPattern(@NonNull List pattern) { + public static LockscreenCredential createPattern(@NonNull List pattern, + byte gridSize) { return new LockscreenCredential(CREDENTIAL_TYPE_PATTERN, - LockPatternUtils.patternToByteArray(pattern), /* hasInvalidChars= */ false); + LockPatternUtils.patternToByteArray(pattern, gridSize), + /* hasInvalidChars= */ false); } /** diff --git a/core/java/com/oplus/theme/OplusThemeUtil.java b/core/java/com/oplus/theme/OplusThemeUtil.java new file mode 100644 index 000000000000..cea1cfd31b83 --- /dev/null +++ b/core/java/com/oplus/theme/OplusThemeUtil.java @@ -0,0 +1,9 @@ +package com.oplus.theme; + +import android.compat.annotation.UnsupportedAppUsage; + +public class OplusThemeUtil { + /** @hide */ + @UnsupportedAppUsage + public static String CUSTOM_THEME_PATH = "/data/theme/com.oplus.camera"; +} diff --git a/core/java/com/oplus/util/OplusTypeCastingHelper.java b/core/java/com/oplus/util/OplusTypeCastingHelper.java new file mode 100644 index 000000000000..4819e9f20b72 --- /dev/null +++ b/core/java/com/oplus/util/OplusTypeCastingHelper.java @@ -0,0 +1,10 @@ +package com.oplus.util; + +public final class OplusTypeCastingHelper { + public static T typeCasting(Class type, Object object) { + if (object != null && type.isInstance(object)) { + return type.cast(object); + } + return null; + } +} diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 9bccf5af7096..55f71c95738e 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -63,6 +64,9 @@ #include "android_util_Binder.h" #include "core_jni_helpers.h" +static jclass gAppVolumeClass; +static jmethodID gAppVolumeCstor; + // ---------------------------------------------------------------------------- namespace audio_flags = android::media::audiopolicy; @@ -669,7 +673,8 @@ static jint android_media_AudioSystem_setDeviceConnectionState(JNIEnv *env, jobj if (status_t statusOfParcel = port.readFromParcel(parcel); statusOfParcel == OK) { status = check_AudioSystem_Command( AudioSystem::setDeviceConnectionState(static_cast(state), - port, static_cast(codec))); + port, static_cast(codec)), + {INVALID_OPERATION}); } else { ALOGE("Failed to read from parcel: %s", statusToString(statusOfParcel).c_str()); status = kAudioStatusError; @@ -916,6 +921,88 @@ android_media_AudioSystem_getMasterBalance(JNIEnv *env, jobject thiz) return balance; } +static jint +android_media_AudioSystem_setAppVolume(JNIEnv *env, jobject thiz, jstring packageName, jfloat value) +{ + const jchar* c_packageName = env->GetStringCritical(packageName, 0); + String8 package8 = String8(reinterpret_cast(c_packageName), env->GetStringLength(packageName)); + env->ReleaseStringCritical(packageName, c_packageName); + return (jint) check_AudioSystem_Command(AudioSystem::setAppVolume(package8, value)); +} + +static jint +android_media_AudioSystem_setAppMute(JNIEnv *env, jobject thiz, jstring packageName, jboolean mute) +{ + const jchar* c_packageName = env->GetStringCritical(packageName, 0); + String8 package8 = String8(reinterpret_cast(c_packageName), env->GetStringLength(packageName)); + env->ReleaseStringCritical(packageName, c_packageName); + return (jint) check_AudioSystem_Command(AudioSystem::setAppMute(package8, mute)); +} + +jint convertAppVolumeFromNative(JNIEnv *env, jobject *jAppVolume, const media::AppVolume *AppVolume) +{ + jint jStatus = (jint)AUDIO_JAVA_SUCCESS; + jstring jPackageName; + jfloat jVolume; + jboolean jMute; + jboolean jActive; + + if (AppVolume == NULL || jAppVolume == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + + jPackageName = env->NewStringUTF(AppVolume->packageName); + jVolume = AppVolume->volume; + jMute = AppVolume->muted; + jActive = AppVolume->active; + + *jAppVolume = env->NewObject(gAppVolumeClass, gAppVolumeCstor, + jPackageName, jMute, jVolume, jActive); + + env->DeleteLocalRef(jPackageName); +exit: + return jStatus; +} + +static jint +android_media_AudioSystem_listAppVolumes(JNIEnv *env, jobject clazz, jobject jVolumes) +{ + ALOGV("listAppVolumes"); + + if (jVolumes == NULL) { + ALOGE("listAppVolumes NULL AppVolume ArrayList"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + if (!env->IsInstanceOf(jVolumes, gArrayListClass)) { + ALOGE("listAppVolumes not an arraylist"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + std::vector volumes; + + jint jStatus = (jint)AUDIO_JAVA_SUCCESS; + status_t status = AudioSystem::listAppVolumes(&volumes); + + if (status != NO_ERROR) { + ALOGE("AudioSystem::listAppVolumes error %d", status); + jStatus = nativeToJavaStatus(status); + return jStatus; + } + + for (size_t i = 0; i < volumes.size(); i++) { + jobject jAppVolume; + jStatus = convertAppVolumeFromNative(env, &jAppVolume, &volumes[i]); + if (jStatus != AUDIO_JAVA_SUCCESS) { + return jStatus; + } + env->CallBooleanMethod(jVolumes, gArrayListMethods.add, jAppVolume); + env->DeleteLocalRef(jAppVolume); + } + + return jStatus; +} + static jint android_media_AudioSystem_getPrimaryOutputSamplingRate(JNIEnv *env, jobject clazz) { @@ -3623,6 +3710,13 @@ static const JNINativeMethod gMethods[] = "(J)V", android_media_AudioSystem_triggerSystemPropertyUpdate), + MAKE_JNI_NATIVE_METHOD("setAppVolume", "(Ljava/lang/String;F)I", + android_media_AudioSystem_setAppVolume), + MAKE_JNI_NATIVE_METHOD("setAppMute", "(Ljava/lang/String;Z)I", + android_media_AudioSystem_setAppMute), + MAKE_JNI_NATIVE_METHOD("listAppVolumes", "(Ljava/util/ArrayList;)I", + android_media_AudioSystem_listAppVolumes), + }; static const JNINativeMethod gEventHandlerMethods[] = @@ -3911,6 +4005,11 @@ int register_android_media_AudioSystem(JNIEnv *env) LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&gVm) != 0); + jclass AppVolumeClass = FindClassOrDie(env, "android/media/AppVolume"); + gAppVolumeClass = MakeGlobalRefOrDie(env, AppVolumeClass); + gAppVolumeCstor = GetMethodIDOrDie(env, AppVolumeClass, "", + "(Ljava/lang/String;ZFZ)V"); + AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback); RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); diff --git a/core/jni/eventlog_helper.h b/core/jni/eventlog_helper.h index 29c023a9bd24..f82801ab3e4a 100644 --- a/core/jni/eventlog_helper.h +++ b/core/jni/eventlog_helper.h @@ -98,8 +98,7 @@ class EventLogHelper { ctx << (value != nullptr ? ScopedUtfChars(env, value).c_str() : "NULL"); return ctx.write(LogID); } - static jint writeEventArray(JNIEnv* env, jobject clazz ATTRIBUTE_UNUSED, jint tag, - jobjectArray value) { + static jint writeEventArray(JNIEnv* env, jobject clazz ATTRIBUTE_UNUSED, jint tag, jobjectArray value) { android_log_event_list ctx(tag); if (value == nullptr) { @@ -107,25 +106,40 @@ class EventLogHelper { return ctx.write(LogID); } - jsize copied = 0, num = env->GetArrayLength(value); - for (; copied < num && copied < 255; ++copied) { + const jsize num = env->GetArrayLength(value); + const jsize limit = num < 255 ? num : 255; // Determine the iteration limit once + + jstring stringItem = nullptr; + const char* utfChars = nullptr; + jint intValue = 0; + jlong longValue = 0; + jfloat floatValue = 0; + + for (jsize copied = 0; copied < limit; ++copied) { if (ctx.status()) break; + ScopedLocalRef item(env, env->GetObjectArrayElement(value, copied)); if (item == nullptr) { ctx << "NULL"; - } else if (env->IsInstanceOf(item.get(), gStringClass)) { - ctx << ScopedUtfChars(env, (jstring) item.get()).c_str(); - } else if (env->IsInstanceOf(item.get(), gIntegerClass)) { - ctx << (int32_t)env->GetIntField(item.get(), gIntegerValueID); - } else if (env->IsInstanceOf(item.get(), gLongClass)) { - ctx << (int64_t)env->GetLongField(item.get(), gLongValueID); - } else if (env->IsInstanceOf(item.get(), gFloatClass)) { - ctx << (float)env->GetFloatField(item.get(), gFloatValueID); } else { - jniThrowException(env, - "java/lang/IllegalArgumentException", - "Invalid payload item type"); - return -1; + if (env->IsInstanceOf(item.get(), gStringClass)) { + stringItem = static_cast(item.get()); + utfChars = env->GetStringUTFChars(stringItem, nullptr); + ctx << utfChars; + env->ReleaseStringUTFChars(stringItem, utfChars); + } else if (env->IsInstanceOf(item.get(), gIntegerClass)) { + intValue = env->GetIntField(item.get(), gIntegerValueID); + ctx << static_cast(intValue); + } else if (env->IsInstanceOf(item.get(), gLongClass)) { + longValue = env->GetLongField(item.get(), gLongValueID); + ctx << static_cast(longValue); + } else if (env->IsInstanceOf(item.get(), gFloatClass)) { + floatValue = env->GetFloatField(item.get(), gFloatValueID); + ctx << static_cast(floatValue); + } else { + jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid payload item type"); + return -1; + } } } return ctx.write(LogID); diff --git a/core/proto/android/server/appstatetracker.proto b/core/proto/android/server/appstatetracker.proto index f5583d4f476f..0d0fb097d963 100644 --- a/core/proto/android/server/appstatetracker.proto +++ b/core/proto/android/server/appstatetracker.proto @@ -25,7 +25,7 @@ option java_multiple_files = true; // Dump from com.android.server.AppStateTracker. // -// Next ID: 14 +// Next ID: 15 message AppStateTrackerProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; @@ -41,6 +41,9 @@ message AppStateTrackerProto { // UIDs currently in the foreground. repeated int32 foreground_uids = 11; + // App ids that are in power-save system exemption list. + repeated int32 power_save_system_exempt_app_ids = 14; + // App ids that are in power-save exemption list. repeated int32 power_save_exempt_app_ids = 3; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d35c66ed719e..54d14f3f3bed 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -824,6 +824,7 @@ + @@ -1299,7 +1300,7 @@ @@ -8465,6 +8466,8 @@ + + + + + + + + + + + + + + + - + diff --git a/core/res/res/drawable-hdpi/stat_sys_data_usb.png b/core/res/res/drawable-hdpi/stat_sys_data_usb.png deleted file mode 100644 index 457d5fc186c6..000000000000 Binary files a/core/res/res/drawable-hdpi/stat_sys_data_usb.png and /dev/null differ diff --git a/core/res/res/drawable-ldpi/stat_sys_data_usb.png b/core/res/res/drawable-ldpi/stat_sys_data_usb.png deleted file mode 100644 index dbf53212f341..000000000000 Binary files a/core/res/res/drawable-ldpi/stat_sys_data_usb.png and /dev/null differ diff --git a/core/res/res/drawable-mdpi/stat_sys_data_usb.png b/core/res/res/drawable-mdpi/stat_sys_data_usb.png deleted file mode 100644 index 9294e5fa4015..000000000000 Binary files a/core/res/res/drawable-mdpi/stat_sys_data_usb.png and /dev/null differ diff --git a/core/res/res/drawable-xhdpi/stat_sys_data_usb.png b/core/res/res/drawable-xhdpi/stat_sys_data_usb.png deleted file mode 100644 index c5fd0134a5b9..000000000000 Binary files a/core/res/res/drawable-xhdpi/stat_sys_data_usb.png and /dev/null differ diff --git a/core/res/res/drawable-xxhdpi/stat_sys_data_usb.png b/core/res/res/drawable-xxhdpi/stat_sys_data_usb.png deleted file mode 100644 index 501b89f4d5d3..000000000000 Binary files a/core/res/res/drawable-xxhdpi/stat_sys_data_usb.png and /dev/null differ diff --git a/core/res/res/drawable/ic_account_circle.xml b/core/res/res/drawable/ic_account_circle.xml index 71691add7322..b83598c51fb7 100644 --- a/core/res/res/drawable/ic_account_circle.xml +++ b/core/res/res/drawable/ic_account_circle.xml @@ -20,8 +20,5 @@ Copyright (C) 2014 The Android Open Source Project android:viewportHeight="24"> - + android:pathData="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M7.07,18.28C7.5,17.38 10.12,16.5 12,16.5C13.88,16.5 16.5,17.38 16.93,18.28C15.57,19.36 13.86,20 12,20C10.14,20 8.43,19.36 7.07,18.28M18.36,16.83C16.93,15.09 13.46,14.5 12,14.5C10.54,14.5 7.07,15.09 5.64,16.83C4.62,15.5 4,13.82 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,13.82 19.38,15.5 18.36,16.83M12,6C10.06,6 8.5,7.56 8.5,9.5C8.5,11.44 10.06,13 12,13C13.94,13 15.5,11.44 15.5,9.5C15.5,7.56 13.94,6 12,6M12,11A1.5,1.5 0 0,1 10.5,9.5A1.5,1.5 0 0,1 12,8A1.5,1.5 0 0,1 13.5,9.5A1.5,1.5 0 0,1 12,11Z" /> diff --git a/core/res/res/drawable/ic_audio_media.xml b/core/res/res/drawable/ic_audio_media.xml index 4ef5340138b1..fd3c0deb75b6 100644 --- a/core/res/res/drawable/ic_audio_media.xml +++ b/core/res/res/drawable/ic_audio_media.xml @@ -22,7 +22,7 @@ + android:pathData="M12,3l0.01,10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55C7.79,13 6,14.79 6,17c0,2.21 1.79,4 4.01,4S14,19.21 14,17V7h4V3H12zM10.01,19c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2C12.01,18.1 11.11,19 10.01,19z"/> diff --git a/core/res/res/drawable/ic_audio_media_mute.xml b/core/res/res/drawable/ic_audio_media_mute.xml index 2be6dc42af4d..210aee81451c 100644 --- a/core/res/res/drawable/ic_audio_media_mute.xml +++ b/core/res/res/drawable/ic_audio_media_mute.xml @@ -22,10 +22,10 @@ + android:pathData="M21.19,21.19L14,14l-2,-2l-9.2,-9.2L1.39,4.22l8.79,8.79c-0.06,0 -0.12,-0.01 -0.18,-0.01C7.79,13 6,14.79 6,17c0,2.21 1.79,4 4.01,4S14,19.21 14,17v-0.17l5.78,5.78L21.19,21.19zM10.01,19c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2C12.01,18.1 11.11,19 10.01,19z"/> + android:pathData="M14,11.17l0,-4.17l4,0l0,-4l-6,0l0,6.17z"/> diff --git a/core/res/res/drawable/ic_audio_ring_notif.xml b/core/res/res/drawable/ic_audio_ring_notif.xml index 54c4074771c2..47a08966aec2 100644 --- a/core/res/res/drawable/ic_audio_ring_notif.xml +++ b/core/res/res/drawable/ic_audio_ring_notif.xml @@ -22,5 +22,8 @@ Copyright (C) 2014 The Android Open Source Project + android:pathData="M18,17v-6c0,-3.07 -1.63,-5.64 -4.5,-6.32V4c0,-0.83 -0.67,-1.5 -1.5,-1.5S10.5,3.17 10.5,4v0.68C7.64,5.36 6,7.92 6,11v6H4v2h10h0.38H20v-2H18zM16,17H8v-6c0,-2.48 1.51,-4.5 4,-4.5s4,2.02 4,4.5V17z"/> + diff --git a/core/res/res/drawable/ic_audio_ring_notif_mute.xml b/core/res/res/drawable/ic_audio_ring_notif_mute.xml index b5915207b7f1..c838fe245d11 100644 --- a/core/res/res/drawable/ic_audio_ring_notif_mute.xml +++ b/core/res/res/drawable/ic_audio_ring_notif_mute.xml @@ -22,5 +22,11 @@ Copyright (C) 2014 The Android Open Source Project + android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4C10,21.1 10.9,22 12,22z"/> + + diff --git a/core/res/res/drawable/ic_audio_vol.xml b/core/res/res/drawable/ic_audio_vol.xml index fc216e5b699a..f66c316b897e 100644 --- a/core/res/res/drawable/ic_audio_vol.xml +++ b/core/res/res/drawable/ic_audio_vol.xml @@ -16,10 +16,10 @@ Copyright (C) 2014 The Android Open Source Project + android:pathData="M3 9v6h4l5 5V4L7 9H3zm7-0.17v6.34L7.83 13H5v-2h2.83L10 8.83zM16.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-0.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89 0.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-0.91 7-4.49 7-8.77 0-4.28-2.99-7.86-7-8.77z" /> diff --git a/core/res/res/drawable/ic_audio_vol_mute.xml b/core/res/res/drawable/ic_audio_vol_mute.xml index 7cf604c614be..d102676fe126 100644 --- a/core/res/res/drawable/ic_audio_vol_mute.xml +++ b/core/res/res/drawable/ic_audio_vol_mute.xml @@ -16,10 +16,10 @@ Copyright (C) 2014 The Android Open Source Project + android:pathData="M4.34 2.93L2.93 4.34 7.29 8.7 7 9H3v6h4l5 5v-6.59l4.18 4.18c-0.65 0.49 -1.38 0.88 -2.18 1.11v2.06c1.34-0.3 2.57-0.92 3.61-1.75l2.05 2.05 1.41-1.41L4.34 2.93zM10 15.17L7.83 13H5v-2h2.83l0.88-0.88L10 11.41v3.76zM19 12c0 0.82-0.15 1.61-0.41 2.34l1.53 1.53c0.56-1.17 0.88 -2.48 0.88 -3.87 0-4.28-2.99-7.86-7-8.77v2.06c2.89 0.86 5 3.54 5 6.71zm-7-8l-1.88 1.88L12 7.76zm4.5 8c0-1.77-1.02-3.29-2.5-4.03v1.79l2.48 2.48c0.01-0.08 0.02 -0.16 0.02 -0.24z" /> diff --git a/core/res/res/drawable/ic_battery.xml b/core/res/res/drawable/ic_battery.xml index bd40f4df505e..318d125ed256 100644 --- a/core/res/res/drawable/ic_battery.xml +++ b/core/res/res/drawable/ic_battery.xml @@ -21,5 +21,5 @@ android:tint="?android:attr/colorControlNormal"> + android:pathData="M16,20H8V6H16M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0 0,0 18,20.67V5.33C18,4.6 17.4,4 16.67,4Z" /> diff --git a/core/res/res/drawable/ic_bt_headphones_a2dp.xml b/core/res/res/drawable/ic_bt_headphones_a2dp.xml index 32f39a39754f..aa53b61387fb 100644 --- a/core/res/res/drawable/ic_bt_headphones_a2dp.xml +++ b/core/res/res/drawable/ic_bt_headphones_a2dp.xml @@ -21,6 +21,5 @@ android:tint="?android:attr/colorControlNormal"> + android:pathData="M19,15v3c0,0.55 -0.45,1 -1,1h-1v-4h2M7,15v4H6c-0.55,0 -1,-0.45 -1,-1v-3h2m5,-13c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 3.13,-7 7,-7s7,3.13 7,7v2h-4v8h3c1.66,0 3,-1.34 3,-3v-7c0,-4.97 -4.03,-9 -9,-9z" /> \ No newline at end of file diff --git a/core/res/res/drawable/ic_bt_headset_hfp.xml b/core/res/res/drawable/ic_bt_headset_hfp.xml index e43fe39409af..f2066ed7c6cd 100644 --- a/core/res/res/drawable/ic_bt_headset_hfp.xml +++ b/core/res/res/drawable/ic_bt_headset_hfp.xml @@ -21,7 +21,5 @@ android:tint="?android:attr/colorControlNormal"> + android:pathData="M12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-1.71C5,6.45 7.96,3.11 11.79,3C15.76,2.89 19,6.06 19,10v2h-4v8h4v1h-6v2h6c1.1,0 2,-0.9 2,-2V10C21,5.03 16.97,1 12,1zM7,14v4H6c-0.55,0 -1,-0.45 -1,-1v-3H7zM19,18h-2v-4h2V18z" /> \ No newline at end of file diff --git a/core/res/res/drawable/ic_bt_pointing_hid.xml b/core/res/res/drawable/ic_bt_pointing_hid.xml index de97e249789f..470d1528b4fb 100644 --- a/core/res/res/drawable/ic_bt_pointing_hid.xml +++ b/core/res/res/drawable/ic_bt_pointing_hid.xml @@ -21,6 +21,5 @@ android:tint="?android:attr/colorControlNormal"> + android:pathData="M20 9c-0.04-4.39-3.6-7.93-8-7.93S4.04 4.61 4 9v6c0 4.42 3.58 8 8 8s8-3.58 8-8V9zm-2 0h-5V3.16c2.81 0.47 4.96 2.9 5 5.84zm-7-5.84V9H6c0.04-2.94 2.19-5.37 5-5.84zM18 15c0 3.31-2.69 6-6 6s-6-2.69-6-6v-4h12v4z" /> \ No newline at end of file diff --git a/core/res/res/drawable/ic_charging_control.xml b/core/res/res/drawable/ic_charging_control.xml new file mode 100644 index 000000000000..830853230a47 --- /dev/null +++ b/core/res/res/drawable/ic_charging_control.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/core/res/res/drawable/ic_doc_folder.xml b/core/res/res/drawable/ic_doc_folder.xml index dcbce010810e..ac29ba665609 100644 --- a/core/res/res/drawable/ic_doc_folder.xml +++ b/core/res/res/drawable/ic_doc_folder.xml @@ -20,5 +20,5 @@ Copyright (C) 2015 The Android Open Source Project android:viewportHeight="24.0"> + android:pathData="M20,18H4V8H20M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z" /> diff --git a/core/res/res/drawable/ic_eject_24dp.xml b/core/res/res/drawable/ic_eject_24dp.xml index 321ee3b6289c..d01461aa0232 100644 --- a/core/res/res/drawable/ic_eject_24dp.xml +++ b/core/res/res/drawable/ic_eject_24dp.xml @@ -20,8 +20,5 @@ Copyright (C) 2015 The Android Open Source Project android:viewportHeight="24.0"> - + android:pathData="M5,17H19V19H5V17M12,5L5.33,15H18.67L12,5M12,8.6L14.93,13H9.07L12,8.6Z" /> diff --git a/core/res/res/drawable/ic_file_copy.xml b/core/res/res/drawable/ic_file_copy.xml index d05b55f1279f..01dff735a402 100644 --- a/core/res/res/drawable/ic_file_copy.xml +++ b/core/res/res/drawable/ic_file_copy.xml @@ -20,6 +20,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/core/res/res/drawable/ic_folder_24dp.xml b/core/res/res/drawable/ic_folder_24dp.xml index 9a386ca45e7a..b6d8a1bbf9c6 100644 --- a/core/res/res/drawable/ic_folder_24dp.xml +++ b/core/res/res/drawable/ic_folder_24dp.xml @@ -16,9 +16,9 @@ Copyright (C) 2015 The Android Open Source Project + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M20,18H4V8H20M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z" /> diff --git a/core/res/res/drawable/ic_link.xml b/core/res/res/drawable/ic_link.xml new file mode 100644 index 000000000000..97322a4f24bd --- /dev/null +++ b/core/res/res/drawable/ic_link.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/res/res/drawable/ic_livedisplay_auto.xml b/core/res/res/drawable/ic_livedisplay_auto.xml new file mode 100644 index 000000000000..39d1f8cc6205 --- /dev/null +++ b/core/res/res/drawable/ic_livedisplay_auto.xml @@ -0,0 +1,29 @@ + + + + + + \ No newline at end of file diff --git a/core/res/res/drawable/ic_livedisplay_day.xml b/core/res/res/drawable/ic_livedisplay_day.xml new file mode 100644 index 000000000000..f454d81f6d4b --- /dev/null +++ b/core/res/res/drawable/ic_livedisplay_day.xml @@ -0,0 +1,30 @@ + + + + + + \ No newline at end of file diff --git a/core/res/res/drawable/ic_livedisplay_night.xml b/core/res/res/drawable/ic_livedisplay_night.xml new file mode 100644 index 000000000000..88a6764c748c --- /dev/null +++ b/core/res/res/drawable/ic_livedisplay_night.xml @@ -0,0 +1,29 @@ + + + + + + \ No newline at end of file diff --git a/core/res/res/drawable/ic_livedisplay_notif.xml b/core/res/res/drawable/ic_livedisplay_notif.xml new file mode 100644 index 000000000000..a7cb8c69646b --- /dev/null +++ b/core/res/res/drawable/ic_livedisplay_notif.xml @@ -0,0 +1,29 @@ + + + + + + \ No newline at end of file diff --git a/core/res/res/drawable/ic_livedisplay_off.xml b/core/res/res/drawable/ic_livedisplay_off.xml new file mode 100644 index 000000000000..f454d81f6d4b --- /dev/null +++ b/core/res/res/drawable/ic_livedisplay_off.xml @@ -0,0 +1,30 @@ + + + + + + \ No newline at end of file diff --git a/core/res/res/drawable/ic_livedisplay_outdoor.xml b/core/res/res/drawable/ic_livedisplay_outdoor.xml new file mode 100644 index 000000000000..66ead51185c2 --- /dev/null +++ b/core/res/res/drawable/ic_livedisplay_outdoor.xml @@ -0,0 +1,27 @@ + + + + + + \ No newline at end of file diff --git a/core/res/res/drawable/ic_lock_lockdown.xml b/core/res/res/drawable/ic_lock_lockdown.xml index b9685d3e7cca..6c6a676a6d35 100644 --- a/core/res/res/drawable/ic_lock_lockdown.xml +++ b/core/res/res/drawable/ic_lock_lockdown.xml @@ -22,5 +22,5 @@ Copyright (C) 2018 The Android Open Source Project + android:pathData="M12,17C10.89,17 10,16.1 10,15C10,13.89 10.89,13 12,13A2,2 0 0,1 14,15A2,2 0 0,1 12,17M18,20V10H6V20H18M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10C4,8.89 4.89,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z" /> diff --git a/core/res/res/drawable/ic_lockscreen_ime.xml b/core/res/res/drawable/ic_lockscreen_ime.xml index 4b81a3c9c460..90492770c5bd 100644 --- a/core/res/res/drawable/ic_lockscreen_ime.xml +++ b/core/res/res/drawable/ic_lockscreen_ime.xml @@ -21,6 +21,5 @@ android:tint="?android:attr/colorControlNormal"> + android:pathData="M4,5A2,2 0 0,0 2,7V17A2,2 0 0,0 4,19H20A2,2 0 0,0 22,17V7A2,2 0 0,0 20,5H4M4,7H20V17H4V7M5,8V10H7V8H5M8,8V10H10V8H8M11,8V10H13V8H11M14,8V10H16V8H14M17,8V10H19V8H17M5,11V13H7V11H5M8,11V13H10V11H8M11,11V13H13V11H11M14,11V13H16V11H14M17,11V13H19V11H17M8,14V16H16V14H8Z" /> \ No newline at end of file diff --git a/core/res/res/drawable/ic_notification_alert.xml b/core/res/res/drawable/ic_notification_alert.xml index c8514acde2ac..f9bd88a9faa7 100644 --- a/core/res/res/drawable/ic_notification_alert.xml +++ b/core/res/res/drawable/ic_notification_alert.xml @@ -25,9 +25,6 @@ Copyright (C) 2016 The Android Open Source Project android:pathData="M7.1,3.6L5.7,2.2C3.3,4.0 1.7,6.8 1.5,10.0l2.0,0.0C3.7,7.3 5.0,5.0 7.1,3.6z" android:fillColor="#FFFFFFFF"/> - diff --git a/core/res/res/drawable/ic_notifications_alerted.xml b/core/res/res/drawable/ic_notifications_alerted.xml index 4bfac37e8408..6bbca37cd48c 100644 --- a/core/res/res/drawable/ic_notifications_alerted.xml +++ b/core/res/res/drawable/ic_notifications_alerted.xml @@ -19,6 +19,9 @@ Copyright (C) 2018 The Android Open Source Project android:viewportWidth="24.0" android:viewportHeight="24.0"> + diff --git a/core/res/res/drawable/ic_perm_device_info.xml b/core/res/res/drawable/ic_perm_device_info.xml index ef91c74620ac..b546992bd39d 100644 --- a/core/res/res/drawable/ic_perm_device_info.xml +++ b/core/res/res/drawable/ic_perm_device_info.xml @@ -16,9 +16,9 @@ + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M17,1.01L7,1C5.9,1 5,1.9 5,3v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3C19,1.9 18.1,1.01 17,1.01zM17,21H7v-1h10V21zM17,18H7V6h10V18zM7,4V3h10v1H7zM11,7h2v2h-2V7zM11,11h2v6h-2V11z"/> diff --git a/core/res/res/drawable/ic_phone.xml b/core/res/res/drawable/ic_phone.xml index be9b094134c8..27ada123149f 100644 --- a/core/res/res/drawable/ic_phone.xml +++ b/core/res/res/drawable/ic_phone.xml @@ -22,8 +22,5 @@ android:autoMirrored="true"> + android:pathData="M6.54 5c0.06 0.89 0.21 1.76 0.45 2.59l-1.2 1.2c-0.41-1.2-0.67-2.47-0.76-3.79h1.51m9.86 12.02c0.85 0.24 1.72 0.39 2.6 0.45 v1.49c-1.32-0.09-2.59-0.35-3.8-0.75l1.2-1.19M7.5 3H4c-0.55 0-1 0.45-1 1 0 9.39 7.61 17 17 17 0.55 0 1-0.45 1-1v-3.49c0-0.55-0.45-1-1-1-1.24 0-2.45-0.2-3.57-0.57-0.1-0.04-0.21-0.05-0.31-0.05-0.26 0-0.51 0.1 -0.71 0.29 l-2.2 2.2c-2.83-1.45-5.15-3.76-6.59-6.59l2.2-2.2c0.28-0.28 0.36 -0.67 0.25 -1.02C8.7 6.45 8.5 5.25 8.5 4c0-0.55-0.45-1-1-1z" /> \ No newline at end of file diff --git a/core/res/res/drawable/ic_print.xml b/core/res/res/drawable/ic_print.xml index 7aa251300448..b72b4d02946e 100644 --- a/core/res/res/drawable/ic_print.xml +++ b/core/res/res/drawable/ic_print.xml @@ -21,6 +21,9 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> + \ No newline at end of file diff --git a/core/res/res/drawable/ic_print_error.xml b/core/res/res/drawable/ic_print_error.xml index 37e51527e399..999e92e39e07 100644 --- a/core/res/res/drawable/ic_print_error.xml +++ b/core/res/res/drawable/ic_print_error.xml @@ -21,6 +21,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> \ No newline at end of file diff --git a/core/res/res/drawable/ic_qs_night_display_on.xml b/core/res/res/drawable/ic_qs_night_display_on.xml index a4755ee256e2..68004f168f2e 100644 --- a/core/res/res/drawable/ic_qs_night_display_on.xml +++ b/core/res/res/drawable/ic_qs_night_display_on.xml @@ -21,6 +21,6 @@ + android:pathData="M17.75,4.09L15.22,6.03L16.13,9.09L13.5,7.28L10.87,9.09L11.78,6.03L9.25,4.09L12.44,4L13.5,1L14.56,4L17.75,4.09M21.25,11L19.61,12.25L20.2,14.23L18.5,13.06L16.8,14.23L17.39,12.25L15.75,11L17.81,10.95L18.5,9L19.19,10.95L21.25,11M18.97,15.95C19.8,15.87 20.69,17.05 20.16,17.8C19.84,18.25 19.5,18.67 19.08,19.07C15.17,23 8.84,23 4.94,19.07C1.03,15.17 1.03,8.83 4.94,4.93C5.34,4.53 5.76,4.17 6.21,3.85C6.96,3.32 8.14,4.21 8.06,5.04C7.79,7.9 8.75,10.87 10.95,13.06C13.14,15.26 16.1,16.22 18.97,15.95M17.33,17.97C14.5,17.81 11.7,16.64 9.53,14.5C7.36,12.31 6.2,9.5 6.04,6.68C3.23,9.82 3.34,14.64 6.35,17.66C9.37,20.67 14.19,20.78 17.33,17.97Z" /> diff --git a/core/res/res/drawable/ic_sd_card_48dp.xml b/core/res/res/drawable/ic_sd_card_48dp.xml index 10fd12054820..d0729fe39533 100644 --- a/core/res/res/drawable/ic_sd_card_48dp.xml +++ b/core/res/res/drawable/ic_sd_card_48dp.xml @@ -16,8 +16,8 @@ Copyright (C) 2015 The Android Open Source Project + android:viewportWidth="24.0" + android:viewportHeight="24.0"> diff --git a/core/res/res/drawable/ic_settings_print.xml b/core/res/res/drawable/ic_settings_print.xml index 68b627cf4dc9..b77e6ba0f2d8 100644 --- a/core/res/res/drawable/ic_settings_print.xml +++ b/core/res/res/drawable/ic_settings_print.xml @@ -21,5 +21,8 @@ android:tint="?android:attr/colorControlNormal"> + android:pathData="M19 8h-1V3H6v5H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zM8 5h8v3H8V5zm8 12v2H8v-4h8v2zm2-2v-2H6v2H4v-4c0-0.55 0.45 -1 1-1h14c0.55 0 1 0.45 1 1v4h-2z" /> + diff --git a/core/res/res/drawable/ic_voice_search_api_material.xml b/core/res/res/drawable/ic_voice_search_api_material.xml index a02621820b62..5aa5b56faa2d 100644 --- a/core/res/res/drawable/ic_voice_search_api_material.xml +++ b/core/res/res/drawable/ic_voice_search_api_material.xml @@ -20,6 +20,9 @@ Copyright (C) 2014 The Android Open Source Project android:viewportHeight="24.0" android:tint="?attr/colorControlNormal"> + diff --git a/core/res/res/drawable/perm_group_call_log.xml b/core/res/res/drawable/perm_group_call_log.xml index a37ed88bebfc..e60770f7b160 100644 --- a/core/res/res/drawable/perm_group_call_log.xml +++ b/core/res/res/drawable/perm_group_call_log.xml @@ -23,7 +23,7 @@ android:viewportHeight="24.0"> + android:pathData="M6.54 5c0.06 0.89 0.21 1.76 0.45 2.59l-1.2 1.2c-0.41-1.2-0.67-2.47-0.76-3.79h1.51m9.86 12.02c0.85 0.24 1.72 0.39 2.6 0.45 v1.49c-1.32-0.09-2.59-0.35-3.8-0.75l1.2-1.19M7.5 3H4c-0.55 0-1 0.45-1 1 0 9.39 7.61 17 17 17 0.55 0 1-0.45 1-1v-3.49c0-0.55-0.45-1-1-1-1.24 0-2.45-0.2-3.57-0.57-0.1-0.04-0.21-0.05-0.31-0.05-0.26 0-0.51 0.1 -0.71 0.29 l-2.2 2.2c-2.83-1.45-5.15-3.76-6.59-6.59l2.2-2.2c0.28-0.28 0.36 -0.67 0.25 -1.02C8.7 6.45 8.5 5.25 8.5 4c0-0.55-0.45-1-1-1z" /> diff --git a/core/res/res/drawable/perm_group_location.xml b/core/res/res/drawable/perm_group_location.xml index a87fc0dc43df..633610ae55f9 100644 --- a/core/res/res/drawable/perm_group_location.xml +++ b/core/res/res/drawable/perm_group_location.xml @@ -15,16 +15,16 @@ limitations under the License. --> + android:pathData="M 10 2 C 6.13 2 3 5.13 3 9 c 0 5.25 7 13 7 13 s 7 -7.75 7 -13 C 17 5.13 13.87 2 10 2 z M 5 9 c 0 -2.76 2.24 -5 5 -5 s 5 2.24 5 5 c 0 2.88 -2.88 7.19 -5 9.88 C 7.92 16.21 5 11.85 5 9 z"/> + android:pathData="M 10 9 m -2.5 0 a 2.5 2.5 0 1 1 5 0 a 2.5 2.5 0 1 1 -5 0"/> diff --git a/core/res/res/drawable/perm_group_phone_calls.xml b/core/res/res/drawable/perm_group_phone_calls.xml index 563222698b46..ff4c138013b1 100644 --- a/core/res/res/drawable/perm_group_phone_calls.xml +++ b/core/res/res/drawable/perm_group_phone_calls.xml @@ -22,8 +22,5 @@ android:viewportHeight="24"> + android:pathData="M6.54 5c0.06 0.89 0.21 1.76 0.45 2.59l-1.2 1.2c-0.41-1.2-0.67-2.47-0.76-3.79h1.51m9.86 12.02c0.85 0.24 1.72 0.39 2.6 0.45 v1.49c-1.32-0.09-2.59-0.35-3.8-0.75l1.2-1.19M7.5 3H4c-0.55 0-1 0.45-1 1 0 9.39 7.61 17 17 17 0.55 0 1-0.45 1-1v-3.49c0-0.55-0.45-1-1-1-1.24 0-2.45-0.2-3.57-0.57-0.1-0.04-0.21-0.05-0.31-0.05-0.26 0-0.51 0.1 -0.71 0.29 l-2.2 2.2c-2.83-1.45-5.15-3.76-6.59-6.59l2.2-2.2c0.28-0.28 0.36 -0.67 0.25 -1.02C8.7 6.45 8.5 5.25 8.5 4c0-0.55-0.45-1-1-1z" /> diff --git a/core/res/res/drawable/stat_sys_data_usb.xml b/core/res/res/drawable/stat_sys_data_usb.xml new file mode 100644 index 000000000000..fb1ad2a47706 --- /dev/null +++ b/core/res/res/drawable/stat_sys_data_usb.xml @@ -0,0 +1,24 @@ + + + + diff --git a/core/res/res/drawable/stat_sys_location.xml b/core/res/res/drawable/stat_sys_location.xml new file mode 100644 index 000000000000..9bb7baee8d91 --- /dev/null +++ b/core/res/res/drawable/stat_sys_location.xml @@ -0,0 +1,5 @@ + + diff --git a/core/res/res/drawable/sym_def_app_icon.xml b/core/res/res/drawable/sym_def_app_icon.xml index 129d38a74750..38d91477fbce 100644 --- a/core/res/res/drawable/sym_def_app_icon.xml +++ b/core/res/res/drawable/sym_def_app_icon.xml @@ -1,7 +1,19 @@ + - - - + diff --git a/core/res/res/drawable/sym_def_app_icon_foreground.xml b/core/res/res/drawable/sym_def_app_icon_foreground.xml new file mode 100644 index 000000000000..0a5a334d10f7 --- /dev/null +++ b/core/res/res/drawable/sym_def_app_icon_foreground.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + diff --git a/core/res/res/drawable/toast_frame.xml b/core/res/res/drawable/toast_frame.xml index a8cdef6d05f1..34987394b2ec 100644 --- a/core/res/res/drawable/toast_frame.xml +++ b/core/res/res/drawable/toast_frame.xml @@ -17,7 +17,7 @@ --> - + diff --git a/core/res/res/layout/alert_dialog_material.xml b/core/res/res/layout/alert_dialog_material.xml index 178505c264a4..b1510fdcb93d 100644 --- a/core/res/res/layout/alert_dialog_material.xml +++ b/core/res/res/layout/alert_dialog_material.xml @@ -54,7 +54,7 @@ android:layout_height="wrap_content" android:paddingEnd="?attr/dialogPreferredPadding" android:paddingStart="?attr/dialogPreferredPadding" - style="@style/TextAppearance.Material.Subhead" /> + style="@style/TextAppearance.DeviceDefault.Subhead" /> +