diff options
105 files changed, 2827 insertions, 670 deletions
diff --git a/api/current.txt b/api/current.txt index 0668d1784d71..5ec04323c564 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3883,6 +3883,7 @@ package android.app { method public android.app.PendingIntent getRunningServiceControlPanel(android.content.ComponentName) throws java.lang.SecurityException; method public deprecated java.util.List<android.app.ActivityManager.RunningServiceInfo> getRunningServices(int) throws java.lang.SecurityException; method public deprecated java.util.List<android.app.ActivityManager.RunningTaskInfo> getRunningTasks(int) throws java.lang.SecurityException; + method public boolean isBackgroundRestricted(); method public deprecated boolean isInLockTaskMode(); method public boolean isLowRamDevice(); method public static boolean isRunningInTestHarness(); @@ -13592,7 +13593,7 @@ package android.graphics { } public class EmbossMaskFilter extends android.graphics.MaskFilter { - ctor public EmbossMaskFilter(float[], float, float, float); + ctor public deprecated EmbossMaskFilter(float[], float, float, float); } public final class ImageDecoder implements java.lang.AutoCloseable { @@ -13605,16 +13606,16 @@ package android.graphics { method public static android.graphics.drawable.Drawable decodeDrawable(android.graphics.ImageDecoder.Source, android.graphics.ImageDecoder.OnHeaderDecodedListener) throws java.io.IOException; method public static android.graphics.drawable.Drawable decodeDrawable(android.graphics.ImageDecoder.Source) throws java.io.IOException; method public android.util.Size getSampledSize(int); - method public void setAllocator(int); - method public void setAsAlphaMask(boolean); - method public void setCrop(android.graphics.Rect); - method public void setMutable(boolean); - method public void setOnPartialImageListener(android.graphics.ImageDecoder.OnPartialImageListener); - method public void setPostProcessor(android.graphics.PostProcessor); - method public void setPreferRamOverQuality(boolean); - method public void setRequireUnpremultiplied(boolean); - method public void setResize(int, int); - method public void setResize(int); + method public android.graphics.ImageDecoder setAllocator(int); + method public android.graphics.ImageDecoder setAsAlphaMask(boolean); + method public android.graphics.ImageDecoder setConserveMemory(boolean); + method public android.graphics.ImageDecoder setCrop(android.graphics.Rect); + method public android.graphics.ImageDecoder setMutable(boolean); + method public android.graphics.ImageDecoder setOnPartialImageListener(android.graphics.ImageDecoder.OnPartialImageListener); + method public android.graphics.ImageDecoder setPostProcessor(android.graphics.PostProcessor); + method public android.graphics.ImageDecoder setRequireUnpremultiplied(boolean); + method public android.graphics.ImageDecoder setResize(int, int); + method public android.graphics.ImageDecoder setResize(int); field public static final int ALLOCATOR_DEFAULT = 0; // 0x0 field public static final int ALLOCATOR_HARDWARE = 3; // 0x3 field public static final int ALLOCATOR_SHARED_MEMORY = 2; // 0x2 @@ -14816,6 +14817,10 @@ package android.graphics.drawable { method public static android.graphics.drawable.Icon createWithResource(android.content.Context, int); method public static android.graphics.drawable.Icon createWithResource(java.lang.String, int); method public int describeContents(); + method public int getResId(); + method public java.lang.String getResPackage(); + method public int getType(); + method public android.net.Uri getUri(); method public android.graphics.drawable.Drawable loadDrawable(android.content.Context); method public void loadDrawableAsync(android.content.Context, android.os.Message); method public void loadDrawableAsync(android.content.Context, android.graphics.drawable.Icon.OnDrawableLoadedListener, android.os.Handler); @@ -14824,6 +14829,11 @@ package android.graphics.drawable { method public android.graphics.drawable.Icon setTintMode(android.graphics.PorterDuff.Mode); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.graphics.drawable.Icon> CREATOR; + field public static final int TYPE_ADAPTIVE_BITMAP = 5; // 0x5 + field public static final int TYPE_BITMAP = 1; // 0x1 + field public static final int TYPE_DATA = 3; // 0x3 + field public static final int TYPE_RESOURCE = 2; // 0x2 + field public static final int TYPE_URI = 4; // 0x4 } public static abstract interface Icon.OnDrawableLoadedListener { diff --git a/api/test-current.txt b/api/test-current.txt index d5b43115c125..21d12c357164 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -12,6 +12,7 @@ package android.app { public class ActivityManager { method public void addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int); method public int getPackageImportance(java.lang.String); + method public long getTotalRam(); method public int getUidImportance(int); method public void removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener); method public void removeStacksInWindowingModes(int[]) throws java.lang.SecurityException; @@ -351,6 +352,7 @@ package android.hardware.display { public final class DisplayManager { method public java.util.List<android.hardware.display.BrightnessChangeEvent> getBrightnessEvents(); + method public android.graphics.Point getStableDisplaySize(); method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); } @@ -446,6 +448,10 @@ package android.net { package android.os { + public static class Build.VERSION { + field public static final int RESOURCES_SDK_INT; + } + public class IncidentManager { method public void reportIncident(android.os.IncidentReportArgs); method public void reportIncident(java.lang.String, byte[]); diff --git a/cmds/incidentd/src/incidentd_util.cpp b/cmds/incidentd/src/incidentd_util.cpp index fc7cec9dbb40..c095f2bcf144 100644 --- a/cmds/incidentd/src/incidentd_util.cpp +++ b/cmds/incidentd/src/incidentd_util.cpp @@ -94,10 +94,10 @@ const char** varargs(const char* first, va_list rest) { // allocate extra 1 for NULL terminator const char** ret = (const char**)malloc(sizeof(const char*) * (numOfArgs + 1)); ret[0] = first; - for (int i = 0; i < numOfArgs; i++) { + for (int i = 1; i < numOfArgs; i++) { const char* arg = va_arg(rest, const char*); - ret[i + 1] = arg; + ret[i] = arg; } - ret[numOfArgs + 1] = NULL; + ret[numOfArgs] = NULL; return ret; } diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index 087e59657247..9b58a14de0be 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -74,7 +74,8 @@ StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap, mAnomalyAlarmMonitor(anomalyAlarmMonitor), mPeriodicAlarmMonitor(periodicAlarmMonitor), mSendBroadcast(sendBroadcast), - mTimeBaseSec(timeBaseSec) { + mTimeBaseSec(timeBaseSec), + mLastLogTimestamp(0) { StatsPullerManager statsPullerManager; statsPullerManager.SetTimeBaseSec(mTimeBaseSec); } @@ -144,9 +145,12 @@ void StatsLogProcessor::onIsolatedUidChangedEventLocked(const LogEvent& event) { } } -// TODO: what if statsd service restarts? How do we know what logs are already processed before? void StatsLogProcessor::OnLogEvent(LogEvent* event) { std::lock_guard<std::mutex> lock(mMetricsMutex); + if (event->GetElapsedTimestampNs() < mLastLogTimestamp) { + return; + } + mLastLogTimestamp = event->GetElapsedTimestampNs(); StatsdStats::getInstance().noteAtomLogged( event->GetTagId(), event->GetElapsedTimestampNs() / NS_PER_SEC); diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 4d9f18509ddb..7a6aa1e26dc3 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -106,6 +106,8 @@ private: const long mTimeBaseSec; + int64_t mLastLogTimestamp; + long mLastPullerCacheClearTimeSec = 0; FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize); diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt index 752b662e050d..8d70a553f1b3 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -659,7 +659,6 @@ Landroid/graphics/drawable/GradientDrawable$GradientState;->mAngle:I Landroid/graphics/drawable/GradientDrawable$GradientState;->mPadding:Landroid/graphics/Rect; Landroid/graphics/drawable/GradientDrawable$GradientState;->mPositions:[F Landroid/graphics/drawable/GradientDrawable;->mPadding:Landroid/graphics/Rect; -Landroid/graphics/drawable/Icon;->getResPackage()Ljava/lang/String; Landroid/graphics/drawable/NinePatchDrawable;->mNinePatchState:Landroid/graphics/drawable/NinePatchDrawable$NinePatchState; Landroid/graphics/drawable/NinePatchDrawable$NinePatchState;->mNinePatch:Landroid/graphics/NinePatch; Landroid/graphics/drawable/StateListDrawable;->extractStateSet(Landroid/util/AttributeSet;)[I diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 03faeeeb91a1..de4d1785745d 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -71,6 +71,7 @@ import com.android.internal.app.procstats.ProcessStats; import com.android.internal.os.RoSystemProperties; import com.android.internal.os.TransferPipe; import com.android.internal.util.FastPrintWriter; +import com.android.internal.util.MemInfoReader; import com.android.server.LocalServices; import org.xmlpull.v1.XmlSerializer; @@ -964,6 +965,17 @@ public class ActivityManager { } /** + * Return the total number of bytes of RAM this device has. + * @hide + */ + @TestApi + public long getTotalRam() { + MemInfoReader memreader = new MemInfoReader(); + memreader.readMemInfo(); + return memreader.getTotalSize(); + } + + /** * Return the maximum number of recents entries that we will maintain and show. * @hide */ @@ -3335,6 +3347,28 @@ public class ActivityManager { } /** + * Query whether the user has enabled background restrictions for this app. + * + * <p> The user may chose to do this, if they see that an app is consuming an unreasonable + * amount of battery while in the background. </p> + * + * <p> If true, any work that the app tries to do will be aggressively restricted while it is in + * the background. At a minimum, jobs and alarms will not execute and foreground services + * cannot be started unless an app activity is in the foreground. </p> + * + * <p><b> Note that these restrictions stay in effect even when the device is charging.</b></p> + * + * @return true if user has enforced background restrictions for this app, false otherwise. + */ + public boolean isBackgroundRestricted() { + try { + return getService().isBackgroundRestricted(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Sets the memory trim mode for a process and schedules a memory trim operation. * * <p><b>Note: this method is only intended for testing framework.</b></p> diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 69054fd8ff15..379944e2e230 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3727,6 +3727,10 @@ public final class ActivityThread extends ClientTransactionHandler { if (localLOGV) Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished); if (r != null && !r.activity.mFinished) { + if (r.getLifecycleState() == ON_RESUME) { + throw new IllegalStateException( + "Trying to resume activity which is already resumed"); + } if (clearHide) { r.hideForNow = false; r.activity.mStartedActivity = false; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index c5b3a4acd339..d76a4f9ff10e 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1738,8 +1738,7 @@ public class AppOpsManager { * @param callback Where to report changes. * @hide */ - // TODO: Uncomment below annotation once b/73559440 is fixed - // @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) + @RequiresPermission(value=android.Manifest.permission.WATCH_APPOPS, conditional=true) public void startWatchingMode(int op, String packageName, final OnOpChangedListener callback) { synchronized (mModeWatchers) { IAppOpsCallback cb = mModeWatchers.get(callback); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index ac301b3cc595..eaa23c6a9b61 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -614,7 +614,7 @@ interface IActivityManager { int sendIntentSender(in IIntentSender target, in IBinder whitelistToken, int code, in Intent intent, in String resolvedType, in IIntentReceiver finishedReceiver, in String requiredPermission, in Bundle options); - + boolean isBackgroundRestricted(in String packageName); // Start of N MR1 transactions void setVrThread(int tid); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 233e09d90f9b..13a6be557dae 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -360,6 +360,23 @@ public class Notification implements Parcelable @Deprecated public RemoteViews headsUpContentView; + private boolean mUsesStandardHeader; + + private static final ArraySet<Integer> STANDARD_LAYOUTS = new ArraySet<>(); + static { + STANDARD_LAYOUTS.add(R.layout.notification_template_material_base); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_base); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_picture); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_media); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media); + STANDARD_LAYOUTS.add(R.layout.notification_template_ambient_header); + STANDARD_LAYOUTS.add(R.layout.notification_template_header); + STANDARD_LAYOUTS.add(R.layout.notification_template_material_ambient); + } + /** * A large bitmap to be shown in the notification content area. * @@ -2534,6 +2551,8 @@ public class Notification implements Parcelable } parcel.writeInt(mGroupAlertBehavior); + + // mUsesStandardHeader is not written because it should be recomputed in listeners } /** @@ -4092,6 +4111,25 @@ public class Notification implements Parcelable } } + /** + * @hide + */ + public boolean usesStandardHeader() { + if (mN.mUsesStandardHeader) { + return true; + } + if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) { + if (mN.contentView == null && mN.bigContentView == null) { + return true; + } + } + boolean contentViewUsesHeader = mN.contentView == null + || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId()); + boolean bigContentViewUsesHeader = mN.bigContentView == null + || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId()); + return contentViewUsesHeader && bigContentViewUsesHeader; + } + private void resetStandardTemplate(RemoteViews contentView) { resetNotificationHeader(contentView); resetContentMargins(contentView); @@ -4123,6 +4161,7 @@ public class Notification implements Parcelable contentView.setViewVisibility(R.id.time, View.GONE); contentView.setImageViewIcon(R.id.profile_badge, null); contentView.setViewVisibility(R.id.profile_badge, View.GONE); + mN.mUsesStandardHeader = false; } private void resetContentMargins(RemoteViews contentView) { @@ -4444,6 +4483,7 @@ public class Notification implements Parcelable bindProfileBadge(contentView); } bindExpandButton(contentView); + mN.mUsesStandardHeader = true; } private void bindExpandButton(RemoteViews contentView) { diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java index 73b5ec440441..545463c124bb 100644 --- a/core/java/android/app/servertransaction/ActivityResultItem.java +++ b/core/java/android/app/servertransaction/ActivityResultItem.java @@ -16,7 +16,7 @@ package android.app.servertransaction; -import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; +import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import android.app.ClientTransactionHandler; @@ -38,8 +38,8 @@ public class ActivityResultItem extends ClientTransactionItem { private List<ResultInfo> mResultInfoList; @Override - public int getPreExecutionState() { - return ON_PAUSE; + public int getPostExecutionState() { + return ON_RESUME; } @Override diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java index 6f2cc007ac27..d94f08b6aac1 100644 --- a/core/java/android/app/servertransaction/ClientTransactionItem.java +++ b/core/java/android/app/servertransaction/ClientTransactionItem.java @@ -32,12 +32,6 @@ import android.os.Parcelable; */ public abstract class ClientTransactionItem implements BaseClientRequest, Parcelable { - /** Get the state in which this callback can be executed. */ - @LifecycleState - public int getPreExecutionState() { - return UNDEFINED; - } - /** Get the state that must follow this callback. */ @LifecycleState public int getPostExecutionState() { diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java index 7dfde73c0534..e5ce3b00c0fd 100644 --- a/core/java/android/app/servertransaction/NewIntentItem.java +++ b/core/java/android/app/servertransaction/NewIntentItem.java @@ -38,11 +38,6 @@ public class NewIntentItem extends ClientTransactionItem { // TODO(lifecycler): Switch new intent handling to this scheme. /*@Override - public int getPreExecutionState() { - return ON_PAUSE; - } - - @Override public int getPostExecutionState() { return ON_RESUME; }*/ diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index 059e0af27a33..0e52b3471504 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -24,6 +24,7 @@ import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME; import static android.app.servertransaction.ActivityLifecycleItem.ON_START; import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED; +import static android.app.servertransaction.TransactionExecutorHelper.lastCallbackRequestingState; import android.app.ActivityThread.ActivityClientRecord; import android.app.ClientTransactionHandler; @@ -48,11 +49,7 @@ public class TransactionExecutor { private ClientTransactionHandler mTransactionHandler; private PendingTransactionActions mPendingActions = new PendingTransactionActions(); - - // Temp holder for lifecycle path. - // No direct transition between two states should take more than one complete cycle of 6 states. - @ActivityLifecycleItem.LifecycleState - private IntArray mLifecycleSequence = new IntArray(6); + private TransactionExecutorHelper mHelper = new TransactionExecutorHelper(); /** Initialize an instance with transaction handler, that will execute all requested actions. */ public TransactionExecutor(ClientTransactionHandler clientTransactionHandler) { @@ -89,13 +86,25 @@ public class TransactionExecutor { final IBinder token = transaction.getActivityToken(); ActivityClientRecord r = mTransactionHandler.getActivityClient(token); + + // In case when post-execution state of the last callback matches the final state requested + // for the activity in this transaction, we won't do the last transition here and do it when + // moving to final state instead (because it may contain additional parameters from server). + final ActivityLifecycleItem finalStateRequest = transaction.getLifecycleStateRequest(); + final int finalState = finalStateRequest != null ? finalStateRequest.getTargetState() + : UNDEFINED; + // Index of the last callback that requests some post-execution state. + final int lastCallbackRequestingState = lastCallbackRequestingState(transaction); + final int size = callbacks.size(); for (int i = 0; i < size; ++i) { final ClientTransactionItem item = callbacks.get(i); log("Resolving callback: " + item); - final int preExecutionState = item.getPreExecutionState(); - if (preExecutionState != UNDEFINED) { - cycleToPath(r, preExecutionState); + final int postExecutionState = item.getPostExecutionState(); + final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r, + item.getPostExecutionState()); + if (closestPreExecutionState != UNDEFINED) { + cycleToPath(r, closestPreExecutionState); } item.execute(mTransactionHandler, token, mPendingActions); @@ -105,9 +114,11 @@ public class TransactionExecutor { r = mTransactionHandler.getActivityClient(token); } - final int postExecutionState = item.getPostExecutionState(); - if (postExecutionState != UNDEFINED) { - cycleToPath(r, postExecutionState); + if (postExecutionState != UNDEFINED && r != null) { + // Skip the very last transition and perform it by explicit state request instead. + final boolean shouldExcludeLastTransition = + i == lastCallbackRequestingState && finalState == postExecutionState; + cycleToPath(r, postExecutionState, shouldExcludeLastTransition); } } } @@ -162,15 +173,15 @@ public class TransactionExecutor { boolean excludeLastState) { final int start = r.getLifecycleState(); log("Cycle from: " + start + " to: " + finish + " excludeLastState:" + excludeLastState); - initLifecyclePath(start, finish, excludeLastState); - performLifecycleSequence(r); + final IntArray path = mHelper.getLifecyclePath(start, finish, excludeLastState); + performLifecycleSequence(r, path); } /** Transition the client through previously initialized state sequence. */ - private void performLifecycleSequence(ActivityClientRecord r) { - final int size = mLifecycleSequence.size(); + private void performLifecycleSequence(ActivityClientRecord r, IntArray path) { + final int size = path.size(); for (int i = 0, state; i < size; i++) { - state = mLifecycleSequence.get(i); + state = path.get(i); log("Transitioning to state: " + state); switch (state) { case ON_CREATE: @@ -195,8 +206,7 @@ public class TransactionExecutor { case ON_DESTROY: mTransactionHandler.handleDestroyActivity(r.token, false /* finishing */, 0 /* configChanges */, false /* getNonConfigInstance */, - "performLifecycleSequence. cycling to:" - + mLifecycleSequence.get(size - 1)); + "performLifecycleSequence. cycling to:" + path.get(size - 1)); break; case ON_RESTART: mTransactionHandler.performRestartActivity(r.token, false /* start */); @@ -207,60 +217,6 @@ public class TransactionExecutor { } } - /** - * Calculate the path through main lifecycle states for an activity and fill - * @link #mLifecycleSequence} with values starting with the state that follows the initial - * state. - */ - public void initLifecyclePath(int start, int finish, boolean excludeLastState) { - mLifecycleSequence.clear(); - if (finish >= start) { - // just go there - for (int i = start + 1; i <= finish; i++) { - mLifecycleSequence.add(i); - } - } else { // finish < start, can't just cycle down - if (start == ON_PAUSE && finish == ON_RESUME) { - // Special case when we can just directly go to resumed state. - mLifecycleSequence.add(ON_RESUME); - } else if (start <= ON_STOP && finish >= ON_START) { - // Restart and go to required state. - - // Go to stopped state first. - for (int i = start + 1; i <= ON_STOP; i++) { - mLifecycleSequence.add(i); - } - // Restart - mLifecycleSequence.add(ON_RESTART); - // Go to required state - for (int i = ON_START; i <= finish; i++) { - mLifecycleSequence.add(i); - } - } else { - // Relaunch and go to required state - - // Go to destroyed state first. - for (int i = start + 1; i <= ON_DESTROY; i++) { - mLifecycleSequence.add(i); - } - // Go to required state - for (int i = ON_CREATE; i <= finish; i++) { - mLifecycleSequence.add(i); - } - } - } - - // Remove last transition in case we want to perform it with some specific params. - if (excludeLastState && mLifecycleSequence.size() != 0) { - mLifecycleSequence.remove(mLifecycleSequence.size() - 1); - } - } - - @VisibleForTesting - public int[] getLifecycleSequence() { - return mLifecycleSequence.toArray(); - } - private static void log(String message) { if (DEBUG_RESOLVER) Slog.d(TAG, message); } diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java new file mode 100644 index 000000000000..7e66fd7a2ead --- /dev/null +++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java @@ -0,0 +1,225 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.servertransaction; + +import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE; +import static android.app.servertransaction.ActivityLifecycleItem.ON_DESTROY; +import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; +import static android.app.servertransaction.ActivityLifecycleItem.ON_RESTART; +import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME; +import static android.app.servertransaction.ActivityLifecycleItem.ON_START; +import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; +import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE; +import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED; + +import android.app.ActivityThread; +import android.util.IntArray; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.List; + +/** + * Helper class for {@link TransactionExecutor} that contains utils for lifecycle path resolution. + * @hide + */ +public class TransactionExecutorHelper { + // A penalty applied to path with destruction when looking for the shortest one. + private static final int DESTRUCTION_PENALTY = 10; + + private static final int[] ON_RESUME_PRE_EXCUTION_STATES = new int[] { ON_START, ON_PAUSE }; + + // Temp holder for lifecycle path. + // No direct transition between two states should take more than one complete cycle of 6 states. + @ActivityLifecycleItem.LifecycleState + private IntArray mLifecycleSequence = new IntArray(6); + + /** + * Calculate the path through main lifecycle states for an activity and fill + * @link #mLifecycleSequence} with values starting with the state that follows the initial + * state. + * <p>NOTE: The returned value is used internally in this class and is not a copy. It's contents + * may change after calling other methods of this class.</p> + */ + @VisibleForTesting + public IntArray getLifecyclePath(int start, int finish, boolean excludeLastState) { + if (start == UNDEFINED || finish == UNDEFINED) { + throw new IllegalArgumentException("Can't resolve lifecycle path for undefined state"); + } + if (start == ON_RESTART || finish == ON_RESTART) { + throw new IllegalArgumentException( + "Can't start or finish in intermittent RESTART state"); + } + if (finish == PRE_ON_CREATE && start != finish) { + throw new IllegalArgumentException("Can only start in pre-onCreate state"); + } + + mLifecycleSequence.clear(); + if (finish >= start) { + // just go there + for (int i = start + 1; i <= finish; i++) { + mLifecycleSequence.add(i); + } + } else { // finish < start, can't just cycle down + if (start == ON_PAUSE && finish == ON_RESUME) { + // Special case when we can just directly go to resumed state. + mLifecycleSequence.add(ON_RESUME); + } else if (start <= ON_STOP && finish >= ON_START) { + // Restart and go to required state. + + // Go to stopped state first. + for (int i = start + 1; i <= ON_STOP; i++) { + mLifecycleSequence.add(i); + } + // Restart + mLifecycleSequence.add(ON_RESTART); + // Go to required state + for (int i = ON_START; i <= finish; i++) { + mLifecycleSequence.add(i); + } + } else { + // Relaunch and go to required state + + // Go to destroyed state first. + for (int i = start + 1; i <= ON_DESTROY; i++) { + mLifecycleSequence.add(i); + } + // Go to required state + for (int i = ON_CREATE; i <= finish; i++) { + mLifecycleSequence.add(i); + } + } + } + + // Remove last transition in case we want to perform it with some specific params. + if (excludeLastState && mLifecycleSequence.size() != 0) { + mLifecycleSequence.remove(mLifecycleSequence.size() - 1); + } + + return mLifecycleSequence; + } + + /** + * Pick a state that goes before provided post-execution state and would require the least + * lifecycle transitions to get to. + * It will also make sure to try avoiding a path with activity destruction and relaunch if + * possible. + * @param r An activity that we're trying to resolve the transition for. + * @param postExecutionState Post execution state to compute for. + * @return One of states that precede the provided post-execution state, or + * {@link ActivityLifecycleItem#UNDEFINED} if there is not path. + */ + @VisibleForTesting + public int getClosestPreExecutionState(ActivityThread.ActivityClientRecord r, + int postExecutionState) { + switch (postExecutionState) { + case UNDEFINED: + return UNDEFINED; + case ON_RESUME: + return getClosestOfStates(r, ON_RESUME_PRE_EXCUTION_STATES); + default: + throw new UnsupportedOperationException("Pre-execution states for state: " + + postExecutionState + " is not supported."); + } + } + + /** + * Pick a state that would require the least lifecycle transitions to get to. + * It will also make sure to try avoiding a path with activity destruction and relaunch if + * possible. + * @param r An activity that we're trying to resolve the transition for. + * @param finalStates An array of valid final states. + * @return One of the provided final states, or {@link ActivityLifecycleItem#UNDEFINED} if none + * were provided or there is not path. + */ + @VisibleForTesting + public int getClosestOfStates(ActivityThread.ActivityClientRecord r, int[] finalStates) { + if (finalStates == null || finalStates.length == 0) { + return UNDEFINED; + } + + final int currentState = r.getLifecycleState(); + int closestState = UNDEFINED; + for (int i = 0, shortestPath = Integer.MAX_VALUE, pathLength; i < finalStates.length; i++) { + getLifecyclePath(currentState, finalStates[i], false /* excludeLastState */); + pathLength = mLifecycleSequence.size(); + if (pathInvolvesDestruction(mLifecycleSequence)) { + pathLength += DESTRUCTION_PENALTY; + } + if (shortestPath > pathLength) { + shortestPath = pathLength; + closestState = finalStates[i]; + } + } + return closestState; + } + + /** + * Check if there is a destruction involved in the path. We want to avoid a lifecycle sequence + * that involves destruction and recreation if there is another path. + */ + private static boolean pathInvolvesDestruction(IntArray lifecycleSequence) { + final int size = lifecycleSequence.size(); + for (int i = 0; i < size; i++) { + if (lifecycleSequence.get(i) == ON_DESTROY) { + return true; + } + } + return false; + } + + /** + * Return the index of the last callback that requests the state in which activity will be after + * execution. If there is a group of callbacks in the end that requests the same specific state + * or doesn't request any - we will find the first one from such group. + * + * E.g. ActivityResult requests RESUMED post-execution state, Configuration does not request any + * specific state. If there is a sequence + * Configuration - ActivityResult - Configuration - ActivityResult + * index 1 will be returned, because ActivityResult request on position 1 will be the last + * request that moves activity to the RESUMED state where it will eventually end. + */ + static int lastCallbackRequestingState(ClientTransaction transaction) { + final List<ClientTransactionItem> callbacks = transaction.getCallbacks(); + if (callbacks == null || callbacks.size() == 0) { + return -1; + } + + // Go from the back of the list to front, look for the request closes to the beginning that + // requests the state in which activity will end after all callbacks are executed. + int lastRequestedState = UNDEFINED; + int lastRequestingCallback = -1; + for (int i = callbacks.size() - 1; i >= 0; i--) { + final ClientTransactionItem callback = callbacks.get(i); + final int postExecutionState = callback.getPostExecutionState(); + if (postExecutionState != UNDEFINED) { + // Found a callback that requests some post-execution state. + if (lastRequestedState == UNDEFINED || lastRequestedState == postExecutionState) { + // It's either a first-from-end callback that requests state or it requests + // the same state as the last one. In both cases, we will use it as the new + // candidate. + lastRequestedState = postExecutionState; + lastRequestingCallback = i; + } else { + break; + } + } + } + + return lastRequestingCallback; + } +} diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java index 0a5795e471f5..b5c69d8897c7 100644 --- a/core/java/android/app/slice/Slice.java +++ b/core/java/android/app/slice/Slice.java @@ -436,7 +436,7 @@ public final class Slice implements Parcelable { } /** - * Add a color to the slice being constructed + * Add an integer to the slice being constructed * @param subType Optional template-specific type information * @see {@link SliceItem#getSubType()} */ @@ -446,7 +446,7 @@ public final class Slice implements Parcelable { } /** - * Add a color to the slice being constructed + * Add an integer to the slice being constructed * @param subType Optional template-specific type information * @see {@link SliceItem#getSubType()} */ diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index a957aed8b806..cec3badd2e6c 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -1872,9 +1872,10 @@ public class IntentFilter implements Parcelable { du.println(sb.toString()); } } - if (mPriority != 0 || mHasPartialTypes) { + if (mPriority != 0 || mOrder != 0 || mHasPartialTypes) { sb.setLength(0); sb.append(prefix); sb.append("mPriority="); sb.append(mPriority); + sb.append(", mOrder="); sb.append(mOrder); sb.append(", mHasPartialTypes="); sb.append(mHasPartialTypes); du.println(sb.toString()); } @@ -1951,6 +1952,7 @@ public class IntentFilter implements Parcelable { dest.writeInt(mHasPartialTypes ? 1 : 0); dest.writeInt(getAutoVerify() ? 1 : 0); dest.writeInt(mInstantAppVisibility); + dest.writeInt(mOrder); } /** @@ -2020,6 +2022,7 @@ public class IntentFilter implements Parcelable { mHasPartialTypes = source.readInt() > 0; setAutoVerify(source.readInt() > 0); setVisibilityToInstantApp(source.readInt()); + mOrder = source.readInt(); } private final boolean findMimeType(String type) { diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 2420b639d678..5f10da1faeab 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -3190,7 +3190,7 @@ public class PackageParser { && (perm.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) == 0 && (perm.info.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE) != PermissionInfo.PROTECTION_SIGNATURE) { - outError[0] = "<permission> protectionLevel specifies a non-instnat flag but is " + outError[0] = "<permission> protectionLevel specifies a non-instant flag but is " + "not based on signature type"; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; @@ -3641,7 +3641,9 @@ public class PackageParser { // getting added to the wrong package. final CachedComponentArgs cachedArgs = new CachedComponentArgs(); int type; - + boolean hasActivityOrder = false; + boolean hasReceiverOrder = false; + boolean hasServiceOrder = false; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { @@ -3657,6 +3659,7 @@ public class PackageParser { return false; } + hasActivityOrder |= (a.order != 0); owner.activities.add(a); } else if (tagName.equals("receiver")) { @@ -3667,6 +3670,7 @@ public class PackageParser { return false; } + hasReceiverOrder |= (a.order != 0); owner.receivers.add(a); } else if (tagName.equals("service")) { @@ -3676,6 +3680,7 @@ public class PackageParser { return false; } + hasServiceOrder |= (s.order != 0); owner.services.add(s); } else if (tagName.equals("provider")) { @@ -3694,6 +3699,7 @@ public class PackageParser { return false; } + hasActivityOrder |= (a.order != 0); owner.activities.add(a); } else if (parser.getName().equals("meta-data")) { @@ -3827,6 +3833,15 @@ public class PackageParser { } } + if (hasActivityOrder) { + Collections.sort(owner.activities, (a1, a2) -> Integer.compare(a2.order, a1.order)); + } + if (hasReceiverOrder) { + Collections.sort(owner.receivers, (r1, r2) -> Integer.compare(r2.order, r1.order)); + } + if (hasServiceOrder) { + Collections.sort(owner.services, (s1, s2) -> Integer.compare(s2.order, s1.order)); + } // Must be ran after the entire {@link ApplicationInfo} has been fully processed and after // every activity info has had a chance to set it from its attributes. setMaxAspectRatio(owner); @@ -4368,6 +4383,7 @@ public class PackageParser { + mArchiveSourcePath + " " + parser.getPositionDescription()); } else { + a.order = Math.max(intent.getOrder(), a.order); a.intents.add(intent); } // adjust activity flags when we implicitly expose it via a browsable filter @@ -4745,6 +4761,7 @@ public class PackageParser { + mArchiveSourcePath + " " + parser.getPositionDescription()); } else { + a.order = Math.max(intent.getOrder(), a.order); a.intents.add(intent); } // adjust activity flags when we implicitly expose it via a browsable filter @@ -4952,6 +4969,7 @@ public class PackageParser { intent.setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT); outInfo.info.flags |= ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP; } + outInfo.order = Math.max(intent.getOrder(), outInfo.order); outInfo.intents.add(intent); } else if (parser.getName().equals("meta-data")) { @@ -5241,6 +5259,7 @@ public class PackageParser { intent.setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT); s.info.flags |= ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP; } + s.order = Math.max(intent.getOrder(), s.order); s.intents.add(intent); } else if (parser.getName().equals("meta-data")) { if ((s.metaData=parseMetaData(res, parser, s.metaData, @@ -5466,6 +5485,10 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestIntentFilter_priority, 0); outInfo.setPriority(priority); + int order = sa.getInt( + com.android.internal.R.styleable.AndroidManifestIntentFilter_order, 0); + outInfo.setOrder(order); + TypedValue v = sa.peekValue( com.android.internal.R.styleable.AndroidManifestIntentFilter_label); if (v != null && (outInfo.labelRes=v.resourceId) == 0) { @@ -7053,6 +7076,8 @@ public class PackageParser { public Bundle metaData; public Package owner; + /** The order of this component in relation to its peers */ + public int order; ComponentName componentName; String componentShortName; @@ -7571,6 +7596,7 @@ public class PackageParser { for (ActivityIntentInfo aii : intents) { aii.activity = this; + order = Math.max(aii.getOrder(), order); } if (info.permission != null) { @@ -7660,6 +7686,7 @@ public class PackageParser { for (ServiceIntentInfo aii : intents) { aii.service = this; + order = Math.max(aii.getOrder(), order); } if (info.permission != null) { diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 2baf539317e9..19b5c45f3a81 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -16,10 +16,11 @@ package android.content.res; +import static android.content.ConfigurationProto.COLOR_MODE; import static android.content.ConfigurationProto.DENSITY_DPI; import static android.content.ConfigurationProto.FONT_SCALE; import static android.content.ConfigurationProto.HARD_KEYBOARD_HIDDEN; -import static android.content.ConfigurationProto.HDR_COLOR_MODE; +import static android.content.ConfigurationProto.KEYBOARD; import static android.content.ConfigurationProto.KEYBOARD_HIDDEN; import static android.content.ConfigurationProto.LOCALES; import static android.content.ConfigurationProto.MCC; @@ -33,7 +34,6 @@ import static android.content.ConfigurationProto.SCREEN_WIDTH_DP; import static android.content.ConfigurationProto.SMALLEST_SCREEN_WIDTH_DP; import static android.content.ConfigurationProto.TOUCHSCREEN; import static android.content.ConfigurationProto.UI_MODE; -import static android.content.ConfigurationProto.WIDE_COLOR_GAMUT; import static android.content.ConfigurationProto.WINDOW_CONFIGURATION; import static android.content.ResourcesConfigurationProto.CONFIGURATION; import static android.content.ResourcesConfigurationProto.SCREEN_HEIGHT_PX; @@ -1095,11 +1095,9 @@ public final class Configuration implements Parcelable, Comparable<Configuration protoOutputStream.write(MNC, mnc); mLocaleList.writeToProto(protoOutputStream, LOCALES); protoOutputStream.write(SCREEN_LAYOUT, screenLayout); - protoOutputStream.write(HDR_COLOR_MODE, - (colorMode & Configuration.COLOR_MODE_HDR_MASK) >> COLOR_MODE_HDR_SHIFT); - protoOutputStream.write(WIDE_COLOR_GAMUT, - colorMode & Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_MASK); + protoOutputStream.write(COLOR_MODE, colorMode); protoOutputStream.write(TOUCHSCREEN, touchscreen); + protoOutputStream.write(KEYBOARD, keyboard); protoOutputStream.write(KEYBOARD_HIDDEN, keyboardHidden); protoOutputStream.write(HARD_KEYBOARD_HIDDEN, hardKeyboardHidden); protoOutputStream.write(NAVIGATION, navigation); diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 6b2059e1c84c..36d5615e6ac2 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -615,6 +615,7 @@ public final class DisplayManager { * @hide */ @SystemApi + @TestApi public Point getStableDisplaySize() { return mGlobal.getStableDisplaySize(); } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 48f56847e88d..6d8831bccdba 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -19,6 +19,7 @@ package android.os; import android.Manifest; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.Application; import android.content.Context; import android.text.TextUtils; @@ -287,6 +288,7 @@ public class Build { * we are operating under, we bump the assumed resource platform version by 1. * @hide */ + @TestApi public static final int RESOURCES_SDK_INT = SDK_INT + ACTIVE_CODENAMES.length; /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 02994079d6ec..569a0db768aa 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10516,8 +10516,11 @@ public final class Settings { * entity_list_default use ":" as delimiter for values. Ex: * * <pre> - * smart_selection_dark_launch (boolean) - * smart_selection_enabled_for_edit_text (boolean) + * model_dark_launch_enabled (boolean) + * smart_selection_enabled (boolean) + * smart_text_share_enabled (boolean) + * smart_linkify_enabled (boolean) + * smart_select_animation_enabled (boolean) * suggest_selection_max_range_length (int) * classify_text_max_range_length (int) * generate_links_max_text_length (int) @@ -10530,7 +10533,7 @@ public final class Settings { * <p> * Type: string * @hide - * see also android.view.textclassifier.TextClassifierConstants + * see also android.view.textclassifier.TextClassificationConstants */ public static final String TEXT_CLASSIFIER_CONSTANTS = "text_classifier_constants"; diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index fbba8abff304..137e820805e2 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -17,6 +17,7 @@ package android.view; import android.annotation.Nullable; +import android.app.AppOpsManager; import android.app.Notification; import android.content.Context; import android.content.res.Resources; @@ -25,6 +26,7 @@ import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.util.ArraySet; import android.util.AttributeSet; import android.widget.ImageView; import android.widget.RemoteViews; @@ -53,6 +55,10 @@ public class NotificationHeaderView extends ViewGroup { private ImageView mExpandButton; private CachingIconView mIcon; private View mProfileBadge; + private View mOverlayIcon; + private View mCameraIcon; + private View mMicIcon; + private View mAppOps; private int mIconColor; private int mOriginalNotificationColor; private boolean mExpanded; @@ -108,6 +114,10 @@ public class NotificationHeaderView extends ViewGroup { mExpandButton = findViewById(com.android.internal.R.id.expand_button); mIcon = findViewById(com.android.internal.R.id.icon); mProfileBadge = findViewById(com.android.internal.R.id.profile_badge); + mCameraIcon = findViewById(com.android.internal.R.id.camera); + mMicIcon = findViewById(com.android.internal.R.id.mic); + mOverlayIcon = findViewById(com.android.internal.R.id.overlay); + mAppOps = findViewById(com.android.internal.R.id.app_ops); } @Override @@ -198,6 +208,11 @@ public class NotificationHeaderView extends ViewGroup { layoutRight = end - paddingEnd; end = layoutLeft = layoutRight - child.getMeasuredWidth(); } + if (child == mAppOps) { + int paddingEnd = mContentEndMargin; + layoutRight = end - paddingEnd; + end = layoutLeft = layoutRight - child.getMeasuredWidth(); + } if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) { int ltrLeft = layoutLeft; layoutLeft = getWidth() - layoutRight; @@ -289,6 +304,22 @@ public class NotificationHeaderView extends ViewGroup { updateExpandButton(); } + /** + * Shows or hides 'app op in use' icons based on app usage. + */ + public void showAppOpsIcons(ArraySet<Integer> appOps) { + if (mOverlayIcon == null || mCameraIcon == null || mMicIcon == null) { + return; + } + + mOverlayIcon.setVisibility(appOps.contains(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) + ? View.VISIBLE : View.GONE); + mCameraIcon.setVisibility(appOps.contains(AppOpsManager.OP_CAMERA) + ? View.VISIBLE : View.GONE); + mMicIcon.setVisibility(appOps.contains(AppOpsManager.OP_RECORD_AUDIO) + ? View.VISIBLE : View.GONE); + } + private void updateExpandButton() { int drawableId; int contentDescriptionId; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f61b6528bd0e..e28522292ac3 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -2953,6 +2953,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 1 PFLAG3_NO_REVEAL_ON_FOCUS * 1 PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT * 1 PFLAG3_SCREEN_READER_FOCUSABLE + * 1 PFLAG3_AGGREGATED_VISIBLE + * 1 PFLAG3_AUTOFILLID_EXPLICITLY_SET + * 1 available * |-------|-------|-------|-------| */ @@ -3243,6 +3246,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static final int PFLAG3_AGGREGATED_VISIBLE = 0x20000000; + /** + * Used to indicate that {@link #mAutofillId} was explicitly set through + * {@link #setAutofillId(AutofillId)}. + */ + private static final int PFLAG3_AUTOFILLID_EXPLICITLY_SET = 0x40000000; + /* End of masks for mPrivateFlags3 */ /** @@ -8205,16 +8214,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @throws IllegalArgumentException if the id is an autofill id associated with a virtual view. */ public void setAutofillId(@Nullable AutofillId id) { + // TODO(b/37566627): add unit / CTS test for all possible combinations below if (android.view.autofill.Helper.sVerbose) { Log.v(VIEW_LOG_TAG, "setAutofill(): from " + mAutofillId + " to " + id); } if (isAttachedToWindow()) { throw new IllegalStateException("Cannot set autofill id when view is attached"); } - if (id.isVirtual()) { + if (id != null && id.isVirtual()) { throw new IllegalStateException("Cannot set autofill id assigned to virtual views"); } + if (id == null && (mPrivateFlags3 & PFLAG3_AUTOFILLID_EXPLICITLY_SET) == 0) { + // Ignore reset because it was never explicitly set before. + return; + } mAutofillId = id; + if (id != null) { + mAutofillViewId = id.getViewId(); + mPrivateFlags3 |= PFLAG3_AUTOFILLID_EXPLICITLY_SET; + } else { + mAutofillViewId = NO_ID; + mPrivateFlags3 &= ~PFLAG3_AUTOFILLID_EXPLICITLY_SET; + } } /** @@ -18524,7 +18545,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Hence prevent the same autofill view id from being restored multiple times. ((BaseSavedState) state).mSavedData &= ~BaseSavedState.AUTOFILL_ID; - mAutofillViewId = baseState.mAutofillViewId; + if ((mPrivateFlags3 & PFLAG3_AUTOFILLID_EXPLICITLY_SET) != 0) { + // Ignore when view already set it through setAutofillId(); + if (android.view.autofill.Helper.sDebug) { + Log.d(VIEW_LOG_TAG, "onRestoreInstanceState(): not setting autofillId to " + + baseState.mAutofillViewId + " because view explicitly set it to " + + mAutofillId); + } + } else { + mAutofillViewId = baseState.mAutofillViewId; + mAutofillId = null; // will be set on demand by getAutofillId() + } } } } diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java index 1789edf1e876..2b335fb09c61 100644 --- a/core/java/android/view/textclassifier/SystemTextClassifier.java +++ b/core/java/android/view/textclassifier/SystemTextClassifier.java @@ -29,6 +29,8 @@ import android.service.textclassifier.ITextClassifierService; import android.service.textclassifier.ITextLinksCallback; import android.service.textclassifier.ITextSelectionCallback; +import com.android.internal.util.Preconditions; + import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -40,13 +42,16 @@ final class SystemTextClassifier implements TextClassifier { private static final String LOG_TAG = "SystemTextClassifier"; private final ITextClassifierService mManagerService; + private final TextClassificationConstants mSettings; private final TextClassifier mFallback; private final String mPackageName; - SystemTextClassifier(Context context) throws ServiceManager.ServiceNotFoundException { + SystemTextClassifier(Context context, TextClassificationConstants settings) + throws ServiceManager.ServiceNotFoundException { mManagerService = ITextClassifierService.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE)); - mFallback = new TextClassifierImpl(context); + mSettings = Preconditions.checkNotNull(settings); + mFallback = new TextClassifierImpl(context, settings); mPackageName = context.getPackageName(); } @@ -108,6 +113,11 @@ final class SystemTextClassifier implements TextClassifier { public TextLinks generateLinks( @NonNull CharSequence text, @Nullable TextLinks.Options options) { Utils.validate(text, false /* allowInMainThread */); + + if (!mSettings.isSmartLinkifyEnabled()) { + return TextClassifier.NO_OP.generateLinks(text, options); + } + try { if (options == null) { options = new TextLinks.Options().setCallingPackageName(mPackageName); diff --git a/core/java/android/view/textclassifier/TextClassifierConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java index 397473be9d4b..32a7f376c4b1 100644 --- a/core/java/android/view/textclassifier/TextClassifierConstants.java +++ b/core/java/android/view/textclassifier/TextClassificationConstants.java @@ -30,11 +30,15 @@ import java.util.StringJoiner; * This is encoded as a key=value list, separated by commas. Ex: * * <pre> - * smart_selection_dark_launch (boolean) - * smart_selection_enabled_for_edit_text (boolean) + * model_dark_launch_enabled (boolean) + * smart_selection_enabled (boolean) + * smart_text_share_enabled (boolean) + * smart_linkify_enabled (boolean) + * smart_select_animation_enabled (boolean) * suggest_selection_max_range_length (int) * classify_text_max_range_length (int) * generate_links_max_text_length (int) + * generate_links_log_sample_rate (int) * entity_list_default (String[]) * entity_list_not_editable (String[]) * entity_list_editable (String[]) @@ -46,20 +50,24 @@ import java.util.StringJoiner; * * Example of setting the values for testing. * adb shell settings put global text_classifier_constants \ - * smart_selection_dark_launch=true,smart_selection_enabled_for_edit_text=true,\ + * model_dark_launch_enabled=true,smart_selection_enabled=true,\ * entity_list_default=phone:address * @hide */ -public final class TextClassifierConstants { +public final class TextClassificationConstants { - private static final String LOG_TAG = "TextClassifierConstants"; + private static final String LOG_TAG = "TextClassificationConstants"; - private static final String SMART_SELECTION_DARK_LAUNCH = - "smart_selection_dark_launch"; - private static final String SMART_SELECTION_ENABLED_FOR_EDIT_TEXT = - "smart_selection_enabled_for_edit_text"; + private static final String MODEL_DARK_LAUNCH_ENABLED = + "model_dark_launch_enabled"; + private static final String SMART_SELECTION_ENABLED = + "smart_selection_enabled"; + private static final String SMART_TEXT_SHARE_ENABLED = + "smart_text_share_enabled"; private static final String SMART_LINKIFY_ENABLED = "smart_linkify_enabled"; + private static final String SMART_SELECT_ANIMATION_ENABLED = + "smart_select_animation_enabled"; private static final String SUGGEST_SELECTION_MAX_RANGE_LENGTH = "suggest_selection_max_range_length"; private static final String CLASSIFY_TEXT_MAX_RANGE_LENGTH = @@ -75,9 +83,11 @@ public final class TextClassifierConstants { private static final String ENTITY_LIST_EDITABLE = "entity_list_editable"; - private static final boolean SMART_SELECTION_DARK_LAUNCH_DEFAULT = false; - private static final boolean SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT = true; + private static final boolean MODEL_DARK_LAUNCH_ENABLED_DEFAULT = false; + private static final boolean SMART_SELECTION_ENABLED_DEFAULT = true; + private static final boolean SMART_TEXT_SHARE_ENABLED_DEFAULT = true; private static final boolean SMART_LINKIFY_ENABLED_DEFAULT = true; + private static final boolean SMART_SELECT_ANIMATION_ENABLED_DEFAULT = true; private static final int SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000; private static final int CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000; private static final int GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT = 100 * 1000; @@ -92,12 +102,11 @@ public final class TextClassifierConstants { .add(TextClassifier.TYPE_DATE_TIME) .add(TextClassifier.TYPE_FLIGHT_NUMBER).toString(); - /** Default settings. */ - static final TextClassifierConstants DEFAULT = new TextClassifierConstants(); - - private final boolean mDarkLaunch; - private final boolean mSuggestSelectionEnabledForEditableText; + private final boolean mModelDarkLaunchEnabled; + private final boolean mSmartSelectionEnabled; + private final boolean mSmartTextShareEnabled; private final boolean mSmartLinkifyEnabled; + private final boolean mSmartSelectionAnimationEnabled; private final int mSuggestSelectionMaxRangeLength; private final int mClassifyTextMaxRangeLength; private final int mGenerateLinksMaxTextLength; @@ -106,20 +115,7 @@ public final class TextClassifierConstants { private final List<String> mEntityListNotEditable; private final List<String> mEntityListEditable; - private TextClassifierConstants() { - mDarkLaunch = SMART_SELECTION_DARK_LAUNCH_DEFAULT; - mSuggestSelectionEnabledForEditableText = SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT; - mSmartLinkifyEnabled = SMART_LINKIFY_ENABLED_DEFAULT; - mSuggestSelectionMaxRangeLength = SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT; - mClassifyTextMaxRangeLength = CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT; - mGenerateLinksMaxTextLength = GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT; - mGenerateLinksLogSampleRate = GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT; - mEntityListDefault = parseEntityList(ENTITY_LIST_DEFAULT_VALUE); - mEntityListNotEditable = mEntityListDefault; - mEntityListEditable = mEntityListDefault; - } - - private TextClassifierConstants(@Nullable String settings) { + private TextClassificationConstants(@Nullable String settings) { final KeyValueListParser parser = new KeyValueListParser(','); try { parser.setString(settings); @@ -127,15 +123,21 @@ public final class TextClassifierConstants { // Failed to parse the settings string, log this and move on with defaults. Slog.e(LOG_TAG, "Bad TextClassifier settings: " + settings); } - mDarkLaunch = parser.getBoolean( - SMART_SELECTION_DARK_LAUNCH, - SMART_SELECTION_DARK_LAUNCH_DEFAULT); - mSuggestSelectionEnabledForEditableText = parser.getBoolean( - SMART_SELECTION_ENABLED_FOR_EDIT_TEXT, - SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT); + mModelDarkLaunchEnabled = parser.getBoolean( + MODEL_DARK_LAUNCH_ENABLED, + MODEL_DARK_LAUNCH_ENABLED_DEFAULT); + mSmartSelectionEnabled = parser.getBoolean( + SMART_SELECTION_ENABLED, + SMART_SELECTION_ENABLED_DEFAULT); + mSmartTextShareEnabled = parser.getBoolean( + SMART_TEXT_SHARE_ENABLED, + SMART_TEXT_SHARE_ENABLED_DEFAULT); mSmartLinkifyEnabled = parser.getBoolean( SMART_LINKIFY_ENABLED, SMART_LINKIFY_ENABLED_DEFAULT); + mSmartSelectionAnimationEnabled = parser.getBoolean( + SMART_SELECT_ANIMATION_ENABLED, + SMART_SELECT_ANIMATION_ENABLED_DEFAULT); mSuggestSelectionMaxRangeLength = parser.getInt( SUGGEST_SELECTION_MAX_RANGE_LENGTH, SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT); @@ -160,22 +162,30 @@ public final class TextClassifierConstants { } /** Load from a settings string. */ - public static TextClassifierConstants loadFromString(String settings) { - return new TextClassifierConstants(settings); + public static TextClassificationConstants loadFromString(String settings) { + return new TextClassificationConstants(settings); } - public boolean isDarkLaunch() { - return mDarkLaunch; + public boolean isModelDarkLaunchEnabled() { + return mModelDarkLaunchEnabled; } - public boolean isSuggestSelectionEnabledForEditableText() { - return mSuggestSelectionEnabledForEditableText; + public boolean isSmartSelectionEnabled() { + return mSmartSelectionEnabled; + } + + public boolean isSmartTextShareEnabled() { + return mSmartTextShareEnabled; } public boolean isSmartLinkifyEnabled() { return mSmartLinkifyEnabled; } + public boolean isSmartSelectionAnimationEnabled() { + return mSmartSelectionAnimationEnabled; + } + public int getSuggestSelectionMaxRangeLength() { return mSuggestSelectionMaxRangeLength; } diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java index 300aef2d172e..fea932cf2a50 100644 --- a/core/java/android/view/textclassifier/TextClassificationManager.java +++ b/core/java/android/view/textclassifier/TextClassificationManager.java @@ -20,6 +20,7 @@ import android.annotation.Nullable; import android.annotation.SystemService; import android.content.Context; import android.os.ServiceManager; +import android.provider.Settings; import android.service.textclassifier.TextClassifierService; import com.android.internal.util.Preconditions; @@ -38,12 +39,15 @@ public final class TextClassificationManager { private final Object mLock = new Object(); private final Context mContext; + private final TextClassificationConstants mSettings; private TextClassifier mTextClassifier; private TextClassifier mSystemTextClassifier; /** @hide */ public TextClassificationManager(Context context) { mContext = Preconditions.checkNotNull(context); + mSettings = TextClassificationConstants.loadFromString(Settings.Global.getString( + context.getContentResolver(), Settings.Global.TEXT_CLASSIFIER_CONSTANTS)); } /** @@ -56,14 +60,14 @@ public final class TextClassificationManager { if (mSystemTextClassifier == null && isSystemTextClassifierEnabled()) { try { Log.d(LOG_TAG, "Initialized SystemTextClassifier"); - mSystemTextClassifier = new SystemTextClassifier(mContext); + mSystemTextClassifier = new SystemTextClassifier(mContext, mSettings); } catch (ServiceManager.ServiceNotFoundException e) { Log.e(LOG_TAG, "Could not initialize SystemTextClassifier", e); } } if (mSystemTextClassifier == null) { Log.d(LOG_TAG, "Using an in-process TextClassifier as the system default"); - mSystemTextClassifier = new TextClassifierImpl(mContext); + mSystemTextClassifier = new TextClassifierImpl(mContext, mSettings); } } return mSystemTextClassifier; @@ -78,7 +82,7 @@ public final class TextClassificationManager { if (isSystemTextClassifierEnabled()) { mTextClassifier = getSystemDefaultTextClassifier(); } else { - mTextClassifier = new TextClassifierImpl(mContext); + mTextClassifier = new TextClassifierImpl(mContext, mSettings); } } return mTextClassifier; @@ -100,4 +104,17 @@ public final class TextClassificationManager { return SYSTEM_TEXT_CLASSIFIER_ENABLED && TextClassifierService.getServiceComponentName(mContext) != null; } + + /** @hide */ + public static TextClassificationConstants getSettings(Context context) { + Preconditions.checkNotNull(context); + final TextClassificationManager tcm = + context.getSystemService(TextClassificationManager.class); + if (tcm != null) { + return tcm.mSettings; + } else { + return TextClassificationConstants.loadFromString(Settings.Global.getString( + context.getContentResolver(), Settings.Global.TEXT_CLASSIFIER_CONSTANTS)); + } + } } diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index d52a30bcc018..0321bb6d62fa 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -329,14 +329,6 @@ public interface TextClassifier { } /** - * Returns this TextClassifier's settings. - * @hide - */ - default TextClassifierConstants getSettings() { - return TextClassifierConstants.DEFAULT; - } - - /** * Configuration object for specifying what entities to identify. * * Configs are initially based on a predefined preset, and can be modified from there. diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 5b7095b2f44f..41f1c69a47ed 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -34,7 +34,6 @@ import android.os.UserManager; import android.provider.Browser; import android.provider.CalendarContract; import android.provider.ContactsContract; -import android.provider.Settings; import android.view.textclassifier.logging.DefaultLogger; import android.view.textclassifier.logging.GenerateLinksLogger; import android.view.textclassifier.logging.Logger; @@ -99,13 +98,13 @@ public final class TextClassifierImpl implements TextClassifier { @GuardedBy("mLoggerLock") // Do not access outside this lock. private Logger mLogger; // Should never be null if mLoggerConfig.get() is not null. - private TextClassifierConstants mSettings; + private final TextClassificationConstants mSettings; - public TextClassifierImpl(Context context) { + public TextClassifierImpl(Context context, TextClassificationConstants settings) { mContext = Preconditions.checkNotNull(context); mFallback = TextClassifier.NO_OP; - mGenerateLinksLogger = new GenerateLinksLogger( - getSettings().getGenerateLinksLogSampleRate()); + mSettings = Preconditions.checkNotNull(settings); + mGenerateLinksLogger = new GenerateLinksLogger(mSettings.getGenerateLinksLogSampleRate()); } /** @inheritDoc */ @@ -117,7 +116,7 @@ public final class TextClassifierImpl implements TextClassifier { try { final int rangeLength = selectionEndIndex - selectionStartIndex; if (text.length() > 0 - && rangeLength <= getSettings().getSuggestSelectionMaxRangeLength()) { + && rangeLength <= mSettings.getSuggestSelectionMaxRangeLength()) { final LocaleList locales = (options == null) ? null : options.getDefaultLocales(); final String localesString = concatenateLocales(locales); final Calendar refTime = Calendar.getInstance(); @@ -126,7 +125,7 @@ public final class TextClassifierImpl implements TextClassifier { final String string = text.toString(); final int start; final int end; - if (getSettings().isDarkLaunch() && !darkLaunchAllowed) { + if (mSettings.isModelDarkLaunchEnabled() && !darkLaunchAllowed) { start = selectionStartIndex; end = selectionEndIndex; } else { @@ -179,7 +178,7 @@ public final class TextClassifierImpl implements TextClassifier { Utils.validate(text, startIndex, endIndex, false /* allowInMainThread */); try { final int rangeLength = endIndex - startIndex; - if (text.length() > 0 && rangeLength <= getSettings().getClassifyTextMaxRangeLength()) { + if (text.length() > 0 && rangeLength <= mSettings.getClassifyTextMaxRangeLength()) { final String string = text.toString(); final LocaleList locales = (options == null) ? null : options.getDefaultLocales(); final String localesString = concatenateLocales(locales); @@ -214,7 +213,7 @@ public final class TextClassifierImpl implements TextClassifier { final String textString = text.toString(); final TextLinks.Builder builder = new TextLinks.Builder(textString); - if (!getSettings().isSmartLinkifyEnabled()) { + if (!mSettings.isSmartLinkifyEnabled()) { return builder.build(); } @@ -226,7 +225,7 @@ public final class TextClassifierImpl implements TextClassifier { options != null && options.getEntityConfig() != null ? options.getEntityConfig().resolveEntityListModifications( getEntitiesForHints(options.getEntityConfig().getHints())) - : getSettings().getEntityListDefault(); + : mSettings.getEntityListDefault(); final TextClassifierImplNative nativeImpl = getNative(defaultLocales); final TextClassifierImplNative.AnnotatedSpan[] annotations = @@ -268,7 +267,7 @@ public final class TextClassifierImpl implements TextClassifier { /** @inheritDoc */ @Override public int getMaxGenerateLinksTextLength() { - return getSettings().getGenerateLinksMaxTextLength(); + return mSettings.getGenerateLinksMaxTextLength(); } private Collection<String> getEntitiesForHints(Collection<String> hints) { @@ -278,11 +277,11 @@ public final class TextClassifierImpl implements TextClassifier { // Use the default if there is no hint, or conflicting ones. final boolean useDefault = editable == notEditable; if (useDefault) { - return getSettings().getEntityListDefault(); + return mSettings.getEntityListDefault(); } else if (editable) { - return getSettings().getEntityListEditable(); + return mSettings.getEntityListEditable(); } else { // notEditable - return getSettings().getEntityListNotEditable(); + return mSettings.getEntityListNotEditable(); } } @@ -298,16 +297,6 @@ public final class TextClassifierImpl implements TextClassifier { } } - /** @hide */ - @Override - public TextClassifierConstants getSettings() { - if (mSettings == null) { - mSettings = TextClassifierConstants.loadFromString(Settings.Global.getString( - mContext.getContentResolver(), Settings.Global.TEXT_CLASSIFIER_CONSTANTS)); - } - return mSettings; - } - private TextClassifierImplNative getNative(LocaleList localeList) throws FileNotFoundException { synchronized (mLock) { diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java index 84c000a379df..ed122a650e66 100644 --- a/core/java/android/webkit/URLUtil.java +++ b/core/java/android/webkit/URLUtil.java @@ -39,7 +39,7 @@ public final class URLUtil { // "file:///android_res/drawable/bar.png". Use "drawable" to refer to // "drawable-hdpi" directory as well. static final String RESOURCE_BASE = "file:///android_res/"; - static final String FILE_BASE = "file://"; + static final String FILE_BASE = "file:"; static final String PROXY_BASE = "file:///cookieless_proxy/"; static final String CONTENT_BASE = "content:"; diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java index 4167ad42cad6..07593a512216 100644 --- a/core/java/android/webkit/WebViewZygote.java +++ b/core/java/android/webkit/WebViewZygote.java @@ -93,13 +93,11 @@ public class WebViewZygote { synchronized (sLock) { sMultiprocessEnabled = enabled; - // When toggling between multi-process being on/off, start or stop the - // zygote. If it is enabled and the zygote is not yet started, launch it. - // Otherwise, kill it. The name may be null if the package information has - // not yet been resolved. - if (enabled) { - connectToZygoteIfNeededLocked(); - } else { + // When multi-process is disabled, kill the zygote. When it is enabled, + // the zygote is not explicitly started here to avoid waiting on the + // zygote launch at boot. Instead, the zygote will be started when it is + // first needed in getProcess(). + if (!enabled) { stopZygoteLocked(); } } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 2e7b2fd65d33..02f35ca0d4d7 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -107,6 +107,7 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.textclassifier.TextClassification; +import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextLinks; import android.widget.AdapterView.OnItemClickListener; import android.widget.TextView.Drawables; @@ -4024,7 +4025,7 @@ public class Editor { private void updateAssistMenuItems(Menu menu) { clearAssistMenuItems(menu); - if (!mTextView.isDeviceProvisioned()) { + if (!shouldEnableAssistMenuItems()) { return; } final TextClassification textClassification = @@ -4097,7 +4098,7 @@ public class Editor { final TextClassification textClassification = getSelectionActionModeHelper().getTextClassification(); - if (!mTextView.isDeviceProvisioned() || textClassification == null) { + if (!shouldEnableAssistMenuItems() || textClassification == null) { // No textClassification result to handle the click. Eat the click. return true; } @@ -4118,6 +4119,12 @@ public class Editor { return true; } + private boolean shouldEnableAssistMenuItems() { + return mTextView.isDeviceProvisioned() + && TextClassificationManager.getSettings(mTextView.getContext()) + .isSmartTextShareEnabled(); + } + @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { getSelectionActionModeHelper().onSelectionAction(item.getItemId()); diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index 6ab09d6cbe74..12ab0ee7d56a 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -34,6 +34,8 @@ import android.text.TextUtils; import android.util.Log; import android.view.ActionMode; import android.view.textclassifier.TextClassification; +import android.view.textclassifier.TextClassificationConstants; +import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassifier; import android.view.textclassifier.TextLinks; import android.view.textclassifier.TextSelection; @@ -65,12 +67,10 @@ public final class SelectionActionModeHelper { private static final String LOG_TAG = "SelectActionModeHelper"; - // TODO: Make this a configurable flag. - private static final boolean SMART_SELECT_ANIMATION_ENABLED = true; - private final Editor mEditor; private final TextView mTextView; private final TextClassificationHelper mTextClassificationHelper; + private final TextClassificationConstants mTextClassificationSettings; private TextClassification mTextClassification; private AsyncTask mTextClassificationAsyncTask; @@ -84,6 +84,7 @@ public final class SelectionActionModeHelper { SelectionActionModeHelper(@NonNull Editor editor) { mEditor = Preconditions.checkNotNull(editor); mTextView = mEditor.getTextView(); + mTextClassificationSettings = TextClassificationManager.getSettings(mTextView.getContext()); mTextClassificationHelper = new TextClassificationHelper( mTextView.getContext(), mTextView.getTextClassifier(), @@ -91,7 +92,7 @@ public final class SelectionActionModeHelper { 0, 1, mTextView.getTextLocales()); mSelectionTracker = new SelectionTracker(mTextView); - if (SMART_SELECT_ANIMATION_ENABLED) { + if (mTextClassificationSettings.isSmartSelectionAnimationEnabled()) { mSmartSelectSprite = new SmartSelectSprite(mTextView.getContext(), editor.getTextView().mHighlightColor, mTextView::invalidate); } else { @@ -104,9 +105,7 @@ public final class SelectionActionModeHelper { */ public void startSelectionActionModeAsync(boolean adjustSelection) { // Check if the smart selection should run for editable text. - adjustSelection &= !mTextView.isTextEditable() - || mTextView.getTextClassifier().getSettings() - .isSuggestSelectionEnabledForEditableText(); + adjustSelection &= mTextClassificationSettings.isSmartSelectionEnabled(); mSelectionTracker.onOriginalSelection( getText(mTextView), @@ -249,7 +248,7 @@ public final class SelectionActionModeHelper { || mTextView.isTextEditable() || actionMode == Editor.TextActionMode.TEXT_LINK)) { // Do not change the selection if TextClassifier should be dark launched. - if (!mTextView.getTextClassifier().getSettings().isDarkLaunch()) { + if (!mTextClassificationSettings.isModelDarkLaunchEnabled()) { Selection.setSelection((Spannable) text, result.mStart, result.mEnd); mTextView.invalidate(); } @@ -450,7 +449,6 @@ public final class SelectionActionModeHelper { selectionEnd = mTextView.getSelectionEnd(); } mTextClassificationHelper.init( - mTextView.getContext(), mTextView.getTextClassifier(), getText(mTextView), selectionStart, selectionEnd, @@ -882,7 +880,8 @@ public final class SelectionActionModeHelper { private static final int TRIM_DELTA = 120; // characters - private Context mContext; + private final Context mContext; + private final boolean mDarkLaunchEnabled; private TextClassifier mTextClassifier; /** The original TextView text. **/ @@ -917,13 +916,15 @@ public final class SelectionActionModeHelper { TextClassificationHelper(Context context, TextClassifier textClassifier, CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) { - init(context, textClassifier, text, selectionStart, selectionEnd, locales); + init(textClassifier, text, selectionStart, selectionEnd, locales); + mContext = Preconditions.checkNotNull(context); + mDarkLaunchEnabled = TextClassificationManager.getSettings(mContext) + .isModelDarkLaunchEnabled(); } @UiThread - public void init(Context context, TextClassifier textClassifier, - CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) { - mContext = Preconditions.checkNotNull(context); + public void init(TextClassifier textClassifier, CharSequence text, + int selectionStart, int selectionEnd, LocaleList locales) { mTextClassifier = Preconditions.checkNotNull(textClassifier); mText = Preconditions.checkNotNull(text).toString(); mLastClassificationText = null; // invalidate. @@ -956,7 +957,7 @@ public final class SelectionActionModeHelper { mSelectionOptions.getDefaultLocales()); } // Do not classify new selection boundaries if TextClassifier should be dark launched. - if (!mTextClassifier.getSettings().isDarkLaunch()) { + if (!mDarkLaunchEnabled) { mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart); mSelectionEnd = Math.min( mText.length(), selection.getSelectionEndIndex() + mTrimStart); diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 8ee31f7c5149..261d89fe6603 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -13348,7 +13348,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = true; private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true; - private static final boolean DEFAULT_READ_BINARY_CPU_TIME = false; + private static final boolean DEFAULT_READ_BINARY_CPU_TIME = true; private static final long DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS = 5_000; private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 10_000; diff --git a/core/jni/android/graphics/YuvToJpegEncoder.cpp b/core/jni/android/graphics/YuvToJpegEncoder.cpp index 5eecd9cf76ef..09adc824e520 100644 --- a/core/jni/android/graphics/YuvToJpegEncoder.cpp +++ b/core/jni/android/graphics/YuvToJpegEncoder.cpp @@ -23,16 +23,28 @@ YuvToJpegEncoder* YuvToJpegEncoder::create(int format, int* strides) { YuvToJpegEncoder::YuvToJpegEncoder(int* strides) : fStrides(strides) { } +struct ErrorMgr { + struct jpeg_error_mgr pub; + jmp_buf jmp; +}; + +void error_exit(j_common_ptr cinfo) { + ErrorMgr* err = (ErrorMgr*) cinfo->err; + (*cinfo->err->output_message) (cinfo); + longjmp(err->jmp, 1); +} + bool YuvToJpegEncoder::encode(SkWStream* stream, void* inYuv, int width, int height, int* offsets, int jpegQuality) { jpeg_compress_struct cinfo; - jpeg_error_mgr err; + ErrorMgr err; skjpeg_destination_mgr sk_wstream(stream); - cinfo.err = jpeg_std_error(&err); - err.error_exit = skjpeg_error_exit; - jmp_buf jmp; - if (setjmp(jmp)) { + cinfo.err = jpeg_std_error(&err.pub); + err.pub.error_exit = error_exit; + + if (setjmp(err.jmp)) { + jpeg_destroy_compress(&cinfo); return false; } jpeg_create_compress(&cinfo); @@ -47,6 +59,8 @@ bool YuvToJpegEncoder::encode(SkWStream* stream, void* inYuv, int width, jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + return true; } diff --git a/core/proto/android/content/configuration.proto b/core/proto/android/content/configuration.proto index 834ecdee6a9f..74b47d2424b2 100644 --- a/core/proto/android/content/configuration.proto +++ b/core/proto/android/content/configuration.proto @@ -35,9 +35,9 @@ message ConfigurationProto { optional uint32 mnc = 3; repeated LocaleProto locales = 4; optional uint32 screen_layout = 5; - optional uint32 hdr_color_mode = 6; - optional uint32 wide_color_gamut = 7; - optional uint32 touchscreen = 8; + optional uint32 color_mode = 6; + optional uint32 touchscreen = 7; + optional uint32 keyboard = 8; optional uint32 keyboard_hidden = 9; optional uint32 hard_keyboard_hidden = 10; optional uint32 navigation = 11; diff --git a/core/res/res/drawable/ic_alert_window_layer.xml b/core/res/res/drawable/ic_alert_window_layer.xml new file mode 100644 index 000000000000..15931b807918 --- /dev/null +++ b/core/res/res/drawable/ic_alert_window_layer.xml @@ -0,0 +1,24 @@ +<!-- +Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="16dp" + android:height="16dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M11.99,18.54l-7.37,-5.73L3,14.07l9,7 9,-7 -1.63,-1.27 -7.38,5.74zM12,16l7.36,-5.73L21,9l-9,-7 -9,7 1.63,1.27L12,16z"/> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_camera.xml b/core/res/res/drawable/ic_camera.xml new file mode 100644 index 000000000000..2921a689ef8a --- /dev/null +++ b/core/res/res/drawable/ic_camera.xml @@ -0,0 +1,27 @@ +<!-- +Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="16dp" + android:height="16dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0" + android:fillColor="#FFFFFF"/> + <path + android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z" + android:fillColor="#FFFFFF"/> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_mic.xml b/core/res/res/drawable/ic_mic.xml new file mode 100644 index 000000000000..3212330278aa --- /dev/null +++ b/core/res/res/drawable/ic_mic.xml @@ -0,0 +1,24 @@ +<!-- +Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="16dp" + android:height="16dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zM17.3,11c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z" + android:fillColor="#FFFFFF"/> +</vector>
\ No newline at end of file diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 20bdf3fe8fa3..c03cf51d6bca 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> - +<!-- extends ViewGroup --> <NotificationHeaderView xmlns:android="http://schemas.android.com/apk/res/android" android:theme="@style/Theme.Material.Notification" @@ -126,5 +126,42 @@ android:visibility="gone" android:contentDescription="@string/notification_work_profile_content_description" /> + + <LinearLayout + android:id="@+id/app_ops" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:orientation="horizontal" > + <ImageButton + android:id="@+id/camera" + android:layout_width="?attr/notificationHeaderIconSize" + android:layout_height="?attr/notificationHeaderIconSize" + android:src="@drawable/ic_camera" + android:tint="@color/notification_secondary_text_color_light" + android:background="?android:selectableItemBackgroundBorderless" + android:layout_marginStart="6dp" + android:visibility="gone" + /> + <ImageButton + android:id="@+id/mic" + android:layout_width="?attr/notificationHeaderIconSize" + android:layout_height="?attr/notificationHeaderIconSize" + android:src="@drawable/ic_mic" + android:tint="@color/notification_secondary_text_color_light" + android:background="?android:selectableItemBackgroundBorderless" + android:layout_marginStart="4dp" + android:visibility="gone" + /> + <ImageButton + android:id="@+id/overlay" + android:layout_width="?attr/notificationHeaderIconSize" + android:layout_height="?attr/notificationHeaderIconSize" + android:src="@drawable/ic_alert_window_layer" + android:tint="@color/notification_secondary_text_color_light" + android:background="?android:selectableItemBackgroundBorderless" + android:layout_marginStart="4dp" + android:visibility="gone" + /> + </LinearLayout> </NotificationHeaderView> diff --git a/core/res/res/values-land/dimens.xml b/core/res/res/values-land/dimens.xml index 4f0c0fbc4835..265eaaf39713 100644 --- a/core/res/res/values-land/dimens.xml +++ b/core/res/res/values-land/dimens.xml @@ -27,6 +27,9 @@ <dimen name="password_keyboard_spacebar_vertical_correction">2dip</dimen> <dimen name="preference_widget_width">72dp</dimen> + <!-- Height of the status bar --> + <dimen name="status_bar_height">@dimen/status_bar_height_landscape</dimen> + <!-- Default height of an action bar. --> <dimen name="action_bar_default_height">40dip</dimen> <!-- Vertical padding around action bar icons. --> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index cfb5784666b9..c4fa190b228d 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2348,6 +2348,16 @@ <attr name="logo" /> <attr name="priority" /> <attr name="autoVerify" /> + <!-- Within an application, multiple intent filters may match a particular + intent. This allows the app author to specify the order filters should + be considered. We don't want to use priority because that is global + across applications. + <p>Only use if you really need to forcibly set the order in which + filters are evaluated. It is preferred to target an activity with a + directed intent instead. + <p>The value is a single integer, with higher numbers considered to + be better. If not specified, the default order is 0. --> + <attr name="order" /> </declare-styleable> <!-- Attributes that can be supplied in an AndroidManifest.xml diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 291826025cc1..7ff96fa7def2 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -32,7 +32,11 @@ <dimen name="toast_y_offset">24dp</dimen> <!-- Height of the status bar --> - <dimen name="status_bar_height">24dp</dimen> + <dimen name="status_bar_height">@dimen/status_bar_height_portrait</dimen> + <!-- Height of the status bar in portrait --> + <dimen name="status_bar_height_portrait">24dp</dimen> + <!-- Height of the status bar in landscape --> + <dimen name="status_bar_height_landscape">@dimen/status_bar_height_portrait</dimen> <!-- Height of area above QQS where battery/time go --> <dimen name="quick_qs_offset_height">48dp</dimen> <!-- Total height of QQS (quick_qs_offset_height + 128) --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 1babd707c781..3b96861e45fc 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -218,6 +218,10 @@ <java-symbol type="id" name="selection_end_handle" /> <java-symbol type="id" name="insertion_handle" /> <java-symbol type="id" name="accessibilityActionClickOnClickableSpan" /> + <java-symbol type="id" name="camera" /> + <java-symbol type="id" name="mic" /> + <java-symbol type="id" name="overlay" /> + <java-symbol type="id" name="app_ops" /> <java-symbol type="attr" name="actionModeShareDrawable" /> <java-symbol type="attr" name="alertDialogCenterButtons" /> @@ -1389,6 +1393,9 @@ <java-symbol type="drawable" name="stat_notify_mmcc_indication_icn" /> <java-symbol type="drawable" name="autofilled_highlight"/> + <java-symbol type="drawable" name="ic_camera" /> + <java-symbol type="drawable" name="ic_mic" /> + <java-symbol type="drawable" name="ic_alert_window_layer" /> <java-symbol type="drawable" name="ic_account_circle" /> <java-symbol type="color" name="user_icon_1" /> @@ -3238,6 +3245,9 @@ <java-symbol type="string" name="keyguard_accessibility_sim_puk_unlock" /> <java-symbol type="string" name="keyguard_accessibility_password_unlock" /> + <java-symbol type="dimen" name="status_bar_height_portrait" /> + <java-symbol type="dimen" name="status_bar_height_landscape" /> + <java-symbol type="string" name="global_action_logout" /> <java-symbol type="string" name="config_mainBuiltInDisplayCutout" /> <java-symbol type="drawable" name="messaging_user" /> diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java index e575650393f0..3eefc362ab86 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java @@ -24,6 +24,9 @@ import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME; import static android.app.servertransaction.ActivityLifecycleItem.ON_START; import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE; +import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED; + +import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertArrayEquals; import static org.mockito.ArgumentMatchers.any; @@ -48,6 +51,10 @@ import org.junit.runner.RunWith; import org.mockito.InOrder; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; /** Test {@link TransactionExecutor} logic. */ @RunWith(AndroidJUnit4.class) @@ -56,6 +63,7 @@ import java.util.ArrayList; public class TransactionExecutorTests { private TransactionExecutor mExecutor; + private TransactionExecutorHelper mExecutorHelper; private ClientTransactionHandler mTransactionHandler; private ActivityClientRecord mClientRecord; @@ -67,6 +75,7 @@ public class TransactionExecutorTests { when(mTransactionHandler.getActivityClient(any())).thenReturn(mClientRecord); mExecutor = spy(new TransactionExecutor(mTransactionHandler)); + mExecutorHelper = new TransactionExecutorHelper(); } @Test @@ -166,10 +175,42 @@ public class TransactionExecutorTests { pathExcludeLast(ON_DESTROY)); } + @Test(expected = IllegalArgumentException.class) + public void testLifecycleUndefinedStartState() { + mClientRecord.setState(UNDEFINED); + path(ON_CREATE); + } + + @Test(expected = IllegalArgumentException.class) + public void testLifecycleUndefinedFinishState() { + mClientRecord.setState(PRE_ON_CREATE); + path(UNDEFINED); + } + + @Test(expected = IllegalArgumentException.class) + public void testLifecycleInvalidPreOnCreateFinishState() { + mClientRecord.setState(ON_CREATE); + path(PRE_ON_CREATE); + } + + @Test(expected = IllegalArgumentException.class) + public void testLifecycleInvalidOnRestartStartState() { + mClientRecord.setState(ON_RESTART); + path(ON_RESUME); + } + + @Test(expected = IllegalArgumentException.class) + public void testLifecycleInvalidOnRestartFinishState() { + mClientRecord.setState(ON_CREATE); + path(ON_RESTART); + } + @Test public void testTransactionResolution() { ClientTransactionItem callback1 = mock(ClientTransactionItem.class); + when(callback1.getPostExecutionState()).thenReturn(UNDEFINED); ClientTransactionItem callback2 = mock(ClientTransactionItem.class); + when(callback2.getPostExecutionState()).thenReturn(UNDEFINED); ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class); IBinder token = mock(IBinder.class); @@ -189,7 +230,7 @@ public class TransactionExecutorTests { } @Test - public void testRequiredStateResolution() { + public void testActivityResultRequiredStateResolution() { ActivityResultItem activityResultItem = ActivityResultItem.obtain(new ArrayList<>()); IBinder token = mock(IBinder.class); @@ -197,20 +238,161 @@ public class TransactionExecutorTests { token /* activityToken */); transaction.addCallback(activityResultItem); + // Verify resolution that should get to onPause + mClientRecord.setState(ON_RESUME); mExecutor.executeCallbacks(transaction); - verify(mExecutor, times(1)).cycleToPath(eq(mClientRecord), eq(ON_PAUSE)); + + // Verify resolution that should get to onStart + mClientRecord.setState(ON_STOP); + mExecutor.executeCallbacks(transaction); + verify(mExecutor, times(1)).cycleToPath(eq(mClientRecord), eq(ON_START)); + } + + @Test + public void testClosestStateResolutionForSameState() { + final int[] allStates = new int[] { + ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}; + + mClientRecord.setState(ON_CREATE); + assertEquals(ON_CREATE, mExecutorHelper.getClosestOfStates(mClientRecord, + shuffledArray(allStates))); + + mClientRecord.setState(ON_START); + assertEquals(ON_START, mExecutorHelper.getClosestOfStates(mClientRecord, + shuffledArray(allStates))); + + mClientRecord.setState(ON_RESUME); + assertEquals(ON_RESUME, mExecutorHelper.getClosestOfStates(mClientRecord, + shuffledArray(allStates))); + + mClientRecord.setState(ON_PAUSE); + assertEquals(ON_PAUSE, mExecutorHelper.getClosestOfStates(mClientRecord, + shuffledArray(allStates))); + + mClientRecord.setState(ON_STOP); + assertEquals(ON_STOP, mExecutorHelper.getClosestOfStates(mClientRecord, + shuffledArray(allStates))); + + mClientRecord.setState(ON_DESTROY); + assertEquals(ON_DESTROY, mExecutorHelper.getClosestOfStates(mClientRecord, + shuffledArray(allStates))); + + mClientRecord.setState(PRE_ON_CREATE); + assertEquals(PRE_ON_CREATE, mExecutorHelper.getClosestOfStates(mClientRecord, + new int[] {PRE_ON_CREATE})); + } + + @Test + public void testClosestStateResolution() { + mClientRecord.setState(PRE_ON_CREATE); + assertEquals(ON_CREATE, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}))); + assertEquals(ON_START, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}))); + assertEquals(ON_RESUME, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}))); + assertEquals(ON_PAUSE, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_PAUSE, ON_STOP, ON_DESTROY}))); + assertEquals(ON_STOP, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_STOP, ON_DESTROY}))); + assertEquals(ON_DESTROY, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_DESTROY}))); + } + + @Test + public void testClosestStateResolutionFromOnCreate() { + mClientRecord.setState(ON_CREATE); + assertEquals(ON_START, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}))); + } + + @Test + public void testClosestStateResolutionFromOnStart() { + mClientRecord.setState(ON_START); + assertEquals(ON_RESUME, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_CREATE, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}))); + assertEquals(ON_CREATE, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_CREATE}))); + } + + @Test + public void testClosestStateResolutionFromOnResume() { + mClientRecord.setState(ON_RESUME); + assertEquals(ON_PAUSE, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_CREATE, ON_START, ON_PAUSE, ON_STOP, ON_DESTROY}))); + assertEquals(ON_DESTROY, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_CREATE, ON_DESTROY}))); + assertEquals(ON_START, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_CREATE, ON_START}))); + assertEquals(ON_CREATE, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_CREATE}))); + } + + @Test + public void testClosestStateResolutionFromOnPause() { + mClientRecord.setState(ON_PAUSE); + assertEquals(ON_RESUME, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_CREATE, ON_START, ON_RESUME, ON_DESTROY}))); + assertEquals(ON_STOP, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_CREATE, ON_START, ON_STOP, ON_DESTROY}))); + assertEquals(ON_START, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_CREATE, ON_START}))); + assertEquals(ON_CREATE, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_CREATE}))); + } + + @Test + public void testClosestStateResolutionFromOnStop() { + mClientRecord.setState(ON_STOP); + assertEquals(ON_RESUME, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_CREATE, ON_RESUME, ON_PAUSE, ON_DESTROY}))); + assertEquals(ON_DESTROY, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_CREATE, ON_DESTROY}))); + assertEquals(ON_START, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_CREATE, ON_START, ON_RESUME, ON_PAUSE}))); + } + + @Test + public void testClosestStateResolutionFromOnDestroy() { + mClientRecord.setState(ON_DESTROY); + assertEquals(ON_CREATE, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( + new int[] {ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP}))); + } + + @Test + public void testClosestStateResolutionToUndefined() { + mClientRecord.setState(ON_CREATE); + assertEquals(UNDEFINED, + mExecutorHelper.getClosestPreExecutionState(mClientRecord, UNDEFINED)); + } + + @Test + public void testClosestStateResolutionToOnResume() { + mClientRecord.setState(ON_DESTROY); + assertEquals(ON_START, + mExecutorHelper.getClosestPreExecutionState(mClientRecord, ON_RESUME)); + mClientRecord.setState(ON_START); + assertEquals(ON_START, + mExecutorHelper.getClosestPreExecutionState(mClientRecord, ON_RESUME)); + mClientRecord.setState(ON_PAUSE); + assertEquals(ON_PAUSE, + mExecutorHelper.getClosestPreExecutionState(mClientRecord, ON_RESUME)); + } + + private static int[] shuffledArray(int[] inputArray) { + final List<Integer> list = Arrays.stream(inputArray).boxed().collect(Collectors.toList()); + Collections.shuffle(list); + return list.stream().mapToInt(Integer::intValue).toArray(); } private int[] path(int finish) { - mExecutor.initLifecyclePath(mClientRecord.getLifecycleState(), finish, - false /* excludeLastState */); - return mExecutor.getLifecycleSequence(); + return mExecutorHelper.getLifecyclePath(mClientRecord.getLifecycleState(), finish, + false /* excludeLastState */).toArray(); } private int[] pathExcludeLast(int finish) { - mExecutor.initLifecyclePath(mClientRecord.getLifecycleState(), finish, - true /* excludeLastState */); - return mExecutor.getLifecycleSequence(); + return mExecutorHelper.getLifecyclePath(mClientRecord.getLifecycleState(), finish, + true /* excludeLastState */).toArray(); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java new file mode 100644 index 000000000000..7f16359ad269 --- /dev/null +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 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.view.textclassifier; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TextClassificationConstantsTest { + + @Test + public void testLoadFromString() { + final String s = "model_dark_launch_enabled=true," + + "smart_selection_enabled=true," + + "smart_text_share_enabled=true," + + "smart_linkify_enabled=true," + + "smart_select_animation_enabled=true," + + "suggest_selection_max_range_length=10," + + "classify_text_max_range_length=11," + + "generate_links_max_text_length=12," + + "generate_links_log_sample_rate=13"; + final TextClassificationConstants constants = + TextClassificationConstants.loadFromString(s); + assertTrue("model_dark_launch_enabled", constants.isModelDarkLaunchEnabled()); + assertTrue("smart_selection_enabled", constants.isSmartSelectionEnabled()); + assertTrue("smart_text_share_enabled", constants.isSmartTextShareEnabled()); + assertTrue("smart_linkify_enabled", constants.isSmartLinkifyEnabled()); + assertTrue("smart_select_animation_enabled", constants.isSmartSelectionAnimationEnabled()); + assertEquals("suggest_selection_max_range_length", + 10, constants.getSuggestSelectionMaxRangeLength()); + assertEquals("classify_text_max_range_length", + 11, constants.getClassifyTextMaxRangeLength()); + assertEquals("generate_links_max_text_length", + 12, constants.getGenerateLinksMaxTextLength()); + assertEquals("generate_links_log_sample_rate", + 13, constants.getGenerateLinksLogSampleRate()); + } + + @Test + public void testLoadFromString_differentValues() { + final String s = "model_dark_launch_enabled=false," + + "smart_selection_enabled=false," + + "smart_text_share_enabled=false," + + "smart_linkify_enabled=false," + + "smart_select_animation_enabled=false," + + "suggest_selection_max_range_length=8," + + "classify_text_max_range_length=7," + + "generate_links_max_text_length=6," + + "generate_links_log_sample_rate=5"; + final TextClassificationConstants constants = + TextClassificationConstants.loadFromString(s); + assertFalse("model_dark_launch_enabled", constants.isModelDarkLaunchEnabled()); + assertFalse("smart_selection_enabled", constants.isSmartSelectionEnabled()); + assertFalse("smart_text_share_enabled", constants.isSmartTextShareEnabled()); + assertFalse("smart_linkify_enabled", constants.isSmartLinkifyEnabled()); + assertFalse("smart_select_animation_enabled", + constants.isSmartSelectionAnimationEnabled()); + assertEquals("suggest_selection_max_range_length", + 8, constants.getSuggestSelectionMaxRangeLength()); + assertEquals("classify_text_max_range_length", + 7, constants.getClassifyTextMaxRangeLength()); + assertEquals("generate_links_max_text_length", + 6, constants.getGenerateLinksMaxTextLength()); + assertEquals("generate_links_log_sample_rate", + 5, constants.getGenerateLinksLogSampleRate()); + } + + @Test + public void testEntityListParsing() { + final TextClassificationConstants constants = TextClassificationConstants.loadFromString( + "entity_list_default=phone," + + "entity_list_not_editable=address:flight," + + "entity_list_editable=date:datetime"); + assertEquals(1, constants.getEntityListDefault().size()); + assertEquals("phone", constants.getEntityListDefault().get(0)); + assertEquals(2, constants.getEntityListNotEditable().size()); + assertEquals("address", constants.getEntityListNotEditable().get(0)); + assertEquals("flight", constants.getEntityListNotEditable().get(1)); + assertEquals(2, constants.getEntityListEditable().size()); + assertEquals("date", constants.getEntityListEditable().get(0)); + assertEquals("datetime", constants.getEntityListEditable().get(1)); + } +} diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierConstantsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierConstantsTest.java deleted file mode 100644 index 984eede55684..000000000000 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierConstantsTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2017 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.view.textclassifier; - -import static org.junit.Assert.assertEquals; - -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class TextClassifierConstantsTest { - - @Test - public void testEntityListParsing() { - final TextClassifierConstants constants = TextClassifierConstants.loadFromString( - "entity_list_default=phone," - + "entity_list_not_editable=address:flight," - + "entity_list_editable=date:datetime"); - assertEquals(1, constants.getEntityListDefault().size()); - assertEquals("phone", constants.getEntityListDefault().get(0)); - assertEquals(2, constants.getEntityListNotEditable().size()); - assertEquals("address", constants.getEntityListNotEditable().get(0)); - assertEquals("flight", constants.getEntityListNotEditable().get(1)); - assertEquals(2, constants.getEntityListEditable().size()); - assertEquals("date", constants.getEntityListEditable().get(0)); - assertEquals("datetime", constants.getEntityListEditable().get(1)); - } -} diff --git a/graphics/java/android/graphics/EmbossMaskFilter.java b/graphics/java/android/graphics/EmbossMaskFilter.java index a9e180fdb6c2..003678ae5a3c 100644 --- a/graphics/java/android/graphics/EmbossMaskFilter.java +++ b/graphics/java/android/graphics/EmbossMaskFilter.java @@ -20,12 +20,15 @@ public class EmbossMaskFilter extends MaskFilter { /** * Create an emboss maskfilter * + * @deprecated This subclass is not supported and should not be instantiated. + * * @param direction array of 3 scalars [x, y, z] specifying the direction of the light source * @param ambient 0...1 amount of ambient light * @param specular coefficient for specular highlights (e.g. 8) * @param blurRadius amount to blur before applying lighting (e.g. 3) * @return the emboss maskfilter */ + @Deprecated public EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius) { if (direction.length < 3) { throw new ArrayIndexOutOfBoundsException(); diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index b6ffe12b0fe7..5abd31a0998a 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -19,6 +19,8 @@ package android.graphics; import static android.system.OsConstants.SEEK_CUR; import static android.system.OsConstants.SEEK_SET; +import static java.lang.annotation.RetentionPolicy.SOURCE; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,32 +30,28 @@ import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager.AssetInputStream; import android.content.res.Resources; import android.graphics.drawable.AnimatedImageDrawable; -import android.graphics.drawable.Drawable; import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.graphics.drawable.NinePatchDrawable; import android.net.Uri; -import android.util.Size; import android.system.ErrnoException; import android.system.Os; import android.util.DisplayMetrics; +import android.util.Size; import android.util.TypedValue; -import libcore.io.IoUtils; import dalvik.system.CloseGuard; -import java.nio.ByteBuffer; +import libcore.io.IoUtils; + import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.lang.ArrayIndexOutOfBoundsException; -import java.lang.AutoCloseable; -import java.lang.NullPointerException; -import java.lang.RuntimeException; import java.lang.annotation.Retention; -import static java.lang.annotation.RetentionPolicy.SOURCE; +import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -494,7 +492,7 @@ public final class ImageDecoder implements AutoCloseable { private int mAllocator = ALLOCATOR_DEFAULT; private boolean mRequireUnpremultiplied = false; private boolean mMutable = false; - private boolean mPreferRamOverQuality = false; + private boolean mConserveMemory = false; private boolean mAsAlphaMask = false; private Rect mCropRect; private Rect mOutPaddingRect; @@ -623,16 +621,18 @@ public final class ImageDecoder implements AutoCloseable { /** * Create a new {@link Source} from a {@link java.nio.ByteBuffer}. * - * <p>The returned {@link Source} effectively takes ownership of the - * {@link java.nio.ByteBuffer}; i.e. no other code should modify it after - * this call.</p> + * <p>Decoding will start from {@link java.nio.ByteBuffer#position()}. The + * position of {@code buffer} will not be affected.</p> * - * Decoding will start from {@link java.nio.ByteBuffer#position()}. The - * position after decoding is undefined. + * <p>Note: If this {@code Source} is passed to {@link #decodeDrawable}, and + * the encoded image is animated, the returned {@link AnimatedImageDrawable} + * will continue reading from the {@code buffer}, so its contents must not + * be modified, even after the {@code AnimatedImageDrawable} is returned. + * {@code buffer}'s contents should never be modified during decode.</p> */ @NonNull public static Source createSource(@NonNull ByteBuffer buffer) { - return new ByteBufferSource(buffer); + return new ByteBufferSource(buffer.slice()); } /** @@ -692,8 +692,9 @@ public final class ImageDecoder implements AutoCloseable { * * @param width must be greater than 0. * @param height must be greater than 0. + * @return this object for chaining. */ - public void setResize(int width, int height) { + public ImageDecoder setResize(int width, int height) { if (width <= 0 || height <= 0) { throw new IllegalArgumentException("Dimensions must be positive! " + "provided (" + width + ", " + height + ")"); @@ -701,6 +702,7 @@ public final class ImageDecoder implements AutoCloseable { mDesiredWidth = width; mDesiredHeight = height; + return this; } /** @@ -710,10 +712,11 @@ public final class ImageDecoder implements AutoCloseable { * {@link #getSampledSize} to {@link #setResize(int, int)}.</p> * * @param sampleSize Sampling rate of the encoded image. + * @return this object for chaining. */ - public void setResize(int sampleSize) { + public ImageDecoder setResize(int sampleSize) { Size size = this.getSampledSize(sampleSize); - this.setResize(size.getWidth(), size.getHeight()); + return this.setResize(size.getWidth(), size.getHeight()); } private boolean requestedResize() { @@ -769,18 +772,20 @@ public final class ImageDecoder implements AutoCloseable { * This is ignored for animated drawables. * * @param allocator Type of allocator to use. + * @return this object for chaining. */ - public void setAllocator(@Allocator int allocator) { + public ImageDecoder setAllocator(@Allocator int allocator) { if (allocator < ALLOCATOR_DEFAULT || allocator > ALLOCATOR_HARDWARE) { throw new IllegalArgumentException("invalid allocator " + allocator); } mAllocator = allocator; + return this; } /** * Specify whether the {@link Bitmap} should have unpremultiplied pixels. * - * By default, ImageDecoder will create a {@link Bitmap} with + * <p>By default, ImageDecoder will create a {@link Bitmap} with * premultiplied pixels, which is required for drawing with the * {@link android.view.View} system (i.e. to a {@link Canvas}). Calling * this method with a value of {@code true} will result in @@ -788,9 +793,13 @@ public final class ImageDecoder implements AutoCloseable { * pixels. See {@link Bitmap#isPremultiplied}. This is incompatible with * {@link #decodeDrawable}; attempting to decode an unpremultiplied * {@link Drawable} will throw an {@link java.lang.IllegalStateException}. + * </p> + * + * @return this object for chaining. */ - public void setRequireUnpremultiplied(boolean requireUnpremultiplied) { + public ImageDecoder setRequireUnpremultiplied(boolean requireUnpremultiplied) { mRequireUnpremultiplied = requireUnpremultiplied; + return this; } /** @@ -805,19 +814,25 @@ public final class ImageDecoder implements AutoCloseable { * <p>For an animated image, the drawing commands drawn on the * {@link Canvas} will be recorded immediately and then applied to each * frame.</p> + * + * @return this object for chaining. */ - public void setPostProcessor(@Nullable PostProcessor p) { + public ImageDecoder setPostProcessor(@Nullable PostProcessor p) { mPostProcessor = p; + return this; } /** * Set (replace) the {@link OnPartialImageListener} on this object. * - * Will be called if there is an error in the input. Without one, a - * partial {@link Bitmap} will be created. + * <p>Will be called if there is an error in the input. Without one, an + * error will result in an Exception being thrown.</p> + * + * @return this object for chaining. */ - public void setOnPartialImageListener(@Nullable OnPartialImageListener l) { + public ImageDecoder setOnPartialImageListener(@Nullable OnPartialImageListener l) { mOnPartialImageListener = l; + return this; } /** @@ -831,9 +846,12 @@ public final class ImageDecoder implements AutoCloseable { * <p>NOT intended as a replacement for * {@link BitmapRegionDecoder#decodeRegion}. This supports all formats, * but merely crops the output.</p> + * + * @return this object for chaining. */ - public void setCrop(@Nullable Rect subset) { + public ImageDecoder setCrop(@Nullable Rect subset) { mCropRect = subset; + return this; } /** @@ -842,10 +860,13 @@ public final class ImageDecoder implements AutoCloseable { * If the image is a nine patch, this Rect will be set to the padding * rectangle during decode. Otherwise it will not be modified. * + * @return this object for chaining. + * * @hide */ - public void setOutPaddingRect(@NonNull Rect outPadding) { + public ImageDecoder setOutPaddingRect(@NonNull Rect outPadding) { mOutPaddingRect = outPadding; + return this; } /** @@ -863,21 +884,31 @@ public final class ImageDecoder implements AutoCloseable { * which would require retrieving the Bitmap from the returned Drawable in * order to modify. Attempting to decode a mutable {@link Drawable} will * throw an {@link java.lang.IllegalStateException}.</p> + * + * @return this object for chaining. */ - public void setMutable(boolean mutable) { + public ImageDecoder setMutable(boolean mutable) { mMutable = mutable; + return this; } /** * Specify whether to potentially save RAM at the expense of quality. * - * Setting this to {@code true} may result in a {@link Bitmap} with a - * denser {@link Bitmap.Config}, depending on the image. For example, for - * an opaque {@link Bitmap}, this may result in a {@link Bitmap.Config} - * with no alpha information. + * <p>Setting this to {@code true} may result in a {@link Bitmap} with a + * denser {@link Bitmap.Config}, depending on the image. For example, an + * opaque {@link Bitmap} with 8 bits or precision for each of its red, + * green and blue components would decode to + * {@link Bitmap.Config#ARGB_8888} by default, but setting this to + * {@code true} will result in decoding to {@link Bitmap.Config#RGB_565}. + * This necessarily lowers the quality of the output, but saves half + * the memory used.</p> + * + * @return this object for chaining. */ - public void setPreferRamOverQuality(boolean preferRamOverQuality) { - mPreferRamOverQuality = preferRamOverQuality; + public ImageDecoder setConserveMemory(boolean conserveMemory) { + mConserveMemory = conserveMemory; + return this; } /** @@ -891,9 +922,12 @@ public final class ImageDecoder implements AutoCloseable { * combine them will result in {@link #decodeDrawable}/ * {@link #decodeBitmap} throwing an * {@link java.lang.IllegalStateException}.</p> + * + * @return this object for chaining. */ - public void setAsAlphaMask(boolean asAlphaMask) { + public ImageDecoder setAsAlphaMask(boolean asAlphaMask) { mAsAlphaMask = asAlphaMask; + return this; } @Override @@ -958,7 +992,7 @@ public final class ImageDecoder implements AutoCloseable { return nDecodeBitmap(mNativePtr, partialImagePtr, postProcessPtr, mDesiredWidth, mDesiredHeight, mCropRect, mMutable, mAllocator, mRequireUnpremultiplied, - mPreferRamOverQuality, mAsAlphaMask); + mConserveMemory, mAsAlphaMask); } private void callHeaderDecoded(@Nullable OnHeaderDecodedListener listener, @@ -1172,7 +1206,7 @@ public final class ImageDecoder implements AutoCloseable { int width, int height, @Nullable Rect cropRect, boolean mutable, int allocator, boolean requireUnpremul, - boolean preferRamOverQuality, boolean asAlphaMask) + boolean conserveMemory, boolean asAlphaMask) throws IOException; private static native Size nGetSampledSize(long nativePtr, int sampleSize); diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java index 749b75941ef9..361fe0bffbbc 100644 --- a/graphics/java/android/graphics/drawable/Icon.java +++ b/graphics/java/android/graphics/drawable/Icon.java @@ -18,11 +18,14 @@ package android.graphics.drawable; import android.annotation.ColorInt; import android.annotation.DrawableRes; -import android.content.res.ColorStateList; +import android.annotation.IdRes; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -60,17 +63,40 @@ import java.util.Objects; public final class Icon implements Parcelable { private static final String TAG = "Icon"; - /** @hide */ + /** + * An icon that was created using {@link Icon#createWithBitmap(Bitmap)}. + * @see #getType + */ public static final int TYPE_BITMAP = 1; - /** @hide */ + /** + * An icon that was created using {@link Icon#createWithResource}. + * @see #getType + */ public static final int TYPE_RESOURCE = 2; - /** @hide */ + /** + * An icon that was created using {@link Icon#createWithData(byte[], int, int)}. + * @see #getType + */ public static final int TYPE_DATA = 3; - /** @hide */ + /** + * An icon that was created using {@link Icon#createWithContentUri} + * or {@link Icon#createWithFilePath(String)}. + * @see #getType + */ public static final int TYPE_URI = 4; - /** @hide */ + /** + * An icon that was created using {@link Icon#createWithAdaptiveBitmap}. + * @see #getType + */ public static final int TYPE_ADAPTIVE_BITMAP = 5; + /** + * @hide + */ + @IntDef({TYPE_BITMAP, TYPE_RESOURCE, TYPE_DATA, TYPE_URI, TYPE_ADAPTIVE_BITMAP}) + public @interface IconType { + } + private static final int VERSION_STREAM_SERIALIZER = 1; private final int mType; @@ -99,14 +125,12 @@ public final class Icon implements Parcelable { private int mInt2; /** - * @return The type of image data held in this Icon. One of - * {@link #TYPE_BITMAP}, - * {@link #TYPE_RESOURCE}, - * {@link #TYPE_DATA}, or - * {@link #TYPE_URI}. - * {@link #TYPE_ADAPTIVE_BITMAP} - * @hide + * Gets the type of the icon provided. + * <p> + * Note that new types may be added later, so callers should guard against other + * types being returned. */ + @IconType public int getType() { return mType; } @@ -179,9 +203,13 @@ public final class Icon implements Parcelable { } /** - * @return The package containing resources for this {@link #TYPE_RESOURCE} Icon. - * @hide + * Gets the package used to create this icon. + * <p> + * Only valid for icons of type {@link #TYPE_RESOURCE}. + * Note: This package may not be available if referenced in the future, and it is + * up to the caller to ensure safety if this package is re-used and/or persisted. */ + @NonNull public String getResPackage() { if (mType != TYPE_RESOURCE) { throw new IllegalStateException("called getResPackage() on " + this); @@ -190,9 +218,13 @@ public final class Icon implements Parcelable { } /** - * @return The resource ID for this {@link #TYPE_RESOURCE} Icon. - * @hide + * Gets the resource used to create this icon. + * <p> + * Only valid for icons of type {@link #TYPE_RESOURCE}. + * Note: This resource may not be available if the application changes at all, and it is + * up to the caller to ensure safety if this resource is re-used and/or persisted. */ + @IdRes public int getResId() { if (mType != TYPE_RESOURCE) { throw new IllegalStateException("called getResId() on " + this); @@ -212,9 +244,13 @@ public final class Icon implements Parcelable { } /** - * @return The {@link android.net.Uri} for this {@link #TYPE_URI} Icon. - * @hide + * Gets the uri used to create this icon. + * <p> + * Only valid for icons of type {@link #TYPE_URI}. + * Note: This uri may not be available in the future, and it is + * up to the caller to ensure safety if this uri is re-used and/or persisted. */ + @NonNull public Uri getUri() { return Uri.parse(getUriString()); } diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java index 7e23ee152ed9..16ef59f201f1 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java @@ -41,7 +41,8 @@ public class DeviceChooserActivity extends Activity { private static final boolean DEBUG = false; private static final String LOG_TAG = "DeviceChooserActivity"; - private ListView mDeviceListView; + View mLoadingIndicator = null; + ListView mDeviceListView; private View mPairButton; private View mCancelButton; @@ -80,8 +81,9 @@ public class DeviceChooserActivity extends Activity { onSelectionUpdate(); } }); - mDeviceListView.addFooterView(getProgressBar(), null, false); + mDeviceListView.addFooterView(mLoadingIndicator = getProgressBar(), null, false); } + getService().mActivity = this; mCancelButton = findViewById(R.id.button_cancel); mCancelButton.setOnClickListener(v -> cancel()); diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java index 1e262314284d..a5f0f24363a5 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java @@ -22,6 +22,7 @@ import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress; import static com.android.internal.util.ArrayUtils.isEmpty; import static com.android.internal.util.CollectionUtils.emptyIfNull; import static com.android.internal.util.CollectionUtils.size; +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.annotation.NonNull; import android.annotation.Nullable; @@ -50,6 +51,7 @@ import android.content.IntentFilter; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.net.wifi.WifiManager; +import android.os.Handler; import android.os.IBinder; import android.os.Parcelable; import android.os.RemoteException; @@ -63,7 +65,9 @@ import android.widget.TextView; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.Preconditions; +import com.android.internal.util.function.pooled.PooledLambda; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -73,6 +77,8 @@ public class DeviceDiscoveryService extends Service { private static final boolean DEBUG = false; private static final String LOG_TAG = "DeviceDiscoveryService"; + private static final long SCAN_TIMEOUT = 20000; + static DeviceDiscoveryService sInstance; private BluetoothAdapter mBluetoothAdapter; @@ -93,6 +99,8 @@ public class DeviceDiscoveryService extends Service { IFindDeviceCallback mFindCallback; ICompanionDeviceDiscoveryServiceCallback mServiceCallback; + boolean mIsScanning = false; + @Nullable DeviceChooserActivity mActivity = null; private final ICompanionDeviceDiscoveryService mBinder = new ICompanionDeviceDiscoveryService.Stub() { @@ -196,6 +204,10 @@ public class DeviceDiscoveryService extends Service { new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); mWifiManager.startScan(); } + mIsScanning = true; + Handler.getMain().sendMessageDelayed( + obtainMessage(DeviceDiscoveryService::stopScan, this), + SCAN_TIMEOUT); } private boolean shouldScan(List<? extends DeviceFilter> mediumSpecificFilters) { @@ -219,6 +231,15 @@ public class DeviceDiscoveryService extends Service { private void stopScan() { if (DEBUG) Log.i(LOG_TAG, "stopScan()"); + if (!mIsScanning) return; + mIsScanning = false; + + DeviceChooserActivity activity = mActivity; + if (activity != null) { + activity.mDeviceListView.removeFooterView(activity.mLoadingIndicator); + mActivity = null; + } + mBluetoothAdapter.cancelDiscovery(); if (mBluetoothBroadcastReceiver != null) { unregisterReceiver(mBluetoothBroadcastReceiver); diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 58d5db32a7e3..a75b147a4901 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -876,47 +876,53 @@ <!-- Summary shown for color space correction preference when its value is overridden by another preference [CHAR LIMIT=35] --> <string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string> - <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery discharging --> - <string name="power_remaining_duration_only">About <xliff:g id="time">%1$s</xliff:g> left</string> - <!-- [CHAR_LIMIT=60] Label for estimated remaining duration of battery discharging --> - <string name="power_remaining_duration_only_enhanced">About <xliff:g id="time">%1$s</xliff:g> left based on your usage</string> - <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging --> - <string name="power_remaining_charging_duration_only"><xliff:g id="time">%1$s</xliff:g> left until fully charged</string> - - <!-- [CHAR_LIMIT=40] Short label for estimated remaining duration of battery charging/discharging --> - <string name="power_remaining_duration_only_short"><xliff:g id="time">%1$s</xliff:g> left</string> - - <!-- [CHAR_LIMIT=60] label for estimated remaining duration of battery when under a certain amount --> - <string name="power_remaining_less_than_duration_only">Less than <xliff:g id="threshold">%1$s</xliff:g> remaining</string> - <!-- [CHAR_LIMIT=60] label for estimated remaining duration of battery when under a certain amount with the percentage --> - <string name="power_remaining_less_than_duration"><xliff:g id="level">%1$s</xliff:g> - Less than <xliff:g id="threshold">%2$s</xliff:g> remaining</string> - - <!-- Used to let users know that they have more than some amount of battery life remaining with percentage. ex: 75% - more than 1 day remaining [CHAR LIMIT = 80] --> - <string name="power_remaining_more_than_subtext"><xliff:g id="level">%1$s</xliff:g>more than <xliff:g id="time_remaining">%2$s</xliff:g> remaining</string> - <!-- Used to let users know that they have more than some amount of battery life remaining. ex: more than 1 day remaining [CHAR LIMIT = 40] --> - <string name="power_remaining_only_more_than_subtext">more than <xliff:g id="time_remaining">%1$s</xliff:g> remaining</string> - - <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> - <string name="power_remaining_duration_only_shutdown_imminent" product="default">phone may shutdown soon</string> - <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> - <string name="power_remaining_duration_only_shutdown_imminent" product="tablet">tablet may shutdown soon</string> - <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> - <string name="power_remaining_duration_only_shutdown_imminent" product="device">device may shutdown soon</string> - - <!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration --> - <string name="power_discharging_duration"><xliff:g id="level">%1$s</xliff:g> - about <xliff:g id="time">%2$s</xliff:g> left</string> - <!-- [CHAR_LIMIT=60] Label for battery level chart when discharging with duration and using enhanced estimate --> - <string name="power_discharging_duration_enhanced"><xliff:g id="level">%1$s</xliff:g> - about <xliff:g id="time">%2$s</xliff:g> left based on your usage</string> - - <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> - <string name="power_remaining_duration_shutdown_imminent" product="default"><xliff:g id="level">%1$s</xliff:g> - phone may shutdown soon</string> - <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> - <string name="power_remaining_duration_shutdown_imminent" product="tablet"><xliff:g id="level">%1$s</xliff:g> - tablet may shutdown soon</string> - <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> - <string name="power_remaining_duration_shutdown_imminent" product="device"><xliff:g id="level">%1$s</xliff:g> - device may shutdown soon</string> + <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery discharging --> + <string name="power_remaining_duration_only">About <xliff:g id="time">%1$s</xliff:g> left</string> + <!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration --> + <string name="power_discharging_duration">About <xliff:g id="time">%1$s</xliff:g> left (<xliff:g id="level">%2$s</xliff:g>)</string> + <!-- [CHAR_LIMIT=60] Label for estimated remaining duration of battery discharging --> + <string name="power_remaining_duration_only_enhanced">About <xliff:g id="time">%1$s</xliff:g> left based on your usage</string> + <!-- [CHAR_LIMIT=60] Label for battery level chart when discharging with duration and using enhanced estimate --> + <string name="power_discharging_duration_enhanced">About <xliff:g id="time">%1$s</xliff:g> left based on your usage (<xliff:g id="level">%2$s</xliff:g>)</string> + <!-- [CHAR_LIMIT=40] Short label for estimated remaining duration of battery charging/discharging --> + <string name="power_remaining_duration_only_short"><xliff:g id="time">%1$s</xliff:g> left</string> + + <!-- [CHAR_LIMIT=100] Label for enhanced estimated time that phone will run out of battery --> + <string name="power_discharge_by_enhanced">Will last until about about <xliff:g id="time">%1$s</xliff:g> based on your usage (<xliff:g id="level">%2$s</xliff:g>)</string> + <!-- [CHAR_LIMIT=100] Label for enhanced estimated time that phone will run out of battery with no percentage --> + <string name="power_discharge_by_only_enhanced">Will last until about about <xliff:g id="time">%1$s</xliff:g> based on your usage</string> + <!-- [CHAR_LIMIT=100] Label for estimated time that phone will run out of battery --> + <string name="power_discharge_by">Will last until about about <xliff:g id="time">%1$s</xliff:g> (<xliff:g id="level">%2$s</xliff:g>)</string> + <!-- [CHAR_LIMIT=100] Label for estimated time that phone will run out of battery --> + <string name="power_discharge_by_only">Will last until about about <xliff:g id="time">%1$s</xliff:g></string> + + <!-- [CHAR_LIMIT=60] label for estimated remaining duration of battery when under a certain amount --> + <string name="power_remaining_less_than_duration_only">Less than <xliff:g id="threshold">%1$s</xliff:g> remaining</string> + <!-- [CHAR_LIMIT=60] label for estimated remaining duration of battery when under a certain amount with the percentage --> + <string name="power_remaining_less_than_duration">Less than <xliff:g id="threshold">%1$s</xliff:g> remaining (<xliff:g id="level">%2$s</xliff:g>)</string> + + <!-- Used to let users know that they have more than some amount of battery life remaining with percentage. ex: 75% - more than 1 day remaining [CHAR LIMIT = 80] --> + <string name="power_remaining_more_than_subtext">More than <xliff:g id="time_remaining">%1$s</xliff:g> remaining (<xliff:g id="level">%2$s</xliff:g>)</string> + <!-- Used to let users know that they have more than some amount of battery life remaining. ex: more than 1 day remaining [CHAR LIMIT = 40] --> + <string name="power_remaining_only_more_than_subtext">More than <xliff:g id="time_remaining">%1$s</xliff:g> remaining</string> + + <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> + <string name="power_remaining_duration_only_shutdown_imminent" product="default">Phone may shutdown soon</string> + <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> + <string name="power_remaining_duration_only_shutdown_imminent" product="tablet">Tablet may shutdown soon</string> + <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> + <string name="power_remaining_duration_only_shutdown_imminent" product="device">Device may shutdown soon</string> + <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> + <string name="power_remaining_duration_shutdown_imminent" product="default">Phone may shutdown soon (<xliff:g id="level">%1$s</xliff:g>)</string> + <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> + <string name="power_remaining_duration_shutdown_imminent" product="tablet">Tablet may shutdown soon (<xliff:g id="level">%1$s</xliff:g>)</string> + <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> + <string name="power_remaining_duration_shutdown_imminent" product="device">Device may shutdown soon (<xliff:g id="level">%1$s</xliff:g>)</string> <!-- [CHAR_LIMIT=40] Label for battery level chart when charging --> <string name="power_charging"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="state">%2$s</xliff:g></string> + <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging --> + <string name="power_remaining_charging_duration_only"><xliff:g id="time">%1$s</xliff:g> left until fully charged</string> <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration --> <string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> until fully charged</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java index 346ca66bcb13..8b3da3944088 100644 --- a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java +++ b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java @@ -17,22 +17,30 @@ package com.android.settingslib.utils; import android.content.Context; +import android.icu.text.DateFormat; import android.icu.text.MeasureFormat; import android.icu.text.MeasureFormat.FormatWidth; import android.icu.util.Measure; import android.icu.util.MeasureUnit; import android.support.annotation.Nullable; import android.text.TextUtils; +import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.R; -import com.android.settingslib.utils.StringUtil; +import java.time.Clock; +import java.time.Instant; +import java.util.Calendar; +import java.util.Date; import java.util.Locale; import java.util.concurrent.TimeUnit; /** Utility class for keeping power related strings consistent**/ public class PowerUtil { + private static final long SEVEN_MINUTES_MILLIS = TimeUnit.MINUTES.toMillis(7); private static final long FIFTEEN_MINUTES_MILLIS = TimeUnit.MINUTES.toMillis(15); private static final long ONE_DAY_MILLIS = TimeUnit.DAYS.toMillis(1); + private static final long TWO_DAYS_MILLIS = TimeUnit.DAYS.toMillis(2); + private static final long ONE_HOUR_MILLIS = TimeUnit.HOURS.toMillis(1); /** * This method produces the text used in various places throughout the system to describe the @@ -57,11 +65,15 @@ public class PowerUtil { FIFTEEN_MINUTES_MILLIS, false /* withSeconds */); return getUnderFifteenString(context, timeString, percentageString); + } else if (drainTimeMs >= TWO_DAYS_MILLIS) { + // just say more than two day if over 48 hours + return getMoreThanTwoDaysString(context, percentageString); } else if (drainTimeMs >= ONE_DAY_MILLIS) { - // just say more than one day if over 24 hours - return getMoreThanOneDayString(context, percentageString); + // show remaining days & hours if more than a day + return getMoreThanOneDayString(context, drainTimeMs, + percentageString, basedOnUsage); } else { - // show a regular time remaining string + // show the time of day we think you'll run out return getRegularTimeRemainingString(context, drainTimeMs, percentageString, basedOnUsage); } @@ -83,44 +95,69 @@ public class PowerUtil { ? context.getString(R.string.power_remaining_less_than_duration_only, timeString) : context.getString( R.string.power_remaining_less_than_duration, - percentageString, - timeString); + timeString, + percentageString); + + } + private static String getMoreThanOneDayString(Context context, long drainTimeMs, + String percentageString, boolean basedOnUsage) { + final long roundedTimeMs = roundToNearestThreshold(drainTimeMs, ONE_HOUR_MILLIS); + CharSequence timeString = StringUtil.formatElapsedTime(context, + roundedTimeMs, + false /* withSeconds */); + + if (TextUtils.isEmpty(percentageString)) { + int id = basedOnUsage + ? R.string.power_remaining_duration_only_enhanced + : R.string.power_remaining_duration_only; + return context.getString(id, timeString); + } else { + int id = basedOnUsage + ? R.string.power_discharging_duration_enhanced + : R.string.power_discharging_duration; + return context.getString(id, timeString, percentageString); + } } - private static String getMoreThanOneDayString(Context context, String percentageString) { + private static String getMoreThanTwoDaysString(Context context, String percentageString) { final Locale currentLocale = context.getResources().getConfiguration().getLocales().get(0); final MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.SHORT); - final Measure daysMeasure = new Measure(1, MeasureUnit.DAY); + final Measure daysMeasure = new Measure(2, MeasureUnit.DAY); return TextUtils.isEmpty(percentageString) ? context.getString(R.string.power_remaining_only_more_than_subtext, frmt.formatMeasures(daysMeasure)) : context.getString( R.string.power_remaining_more_than_subtext, - percentageString, - frmt.formatMeasures(daysMeasure)); + frmt.formatMeasures(daysMeasure), + percentageString); } private static String getRegularTimeRemainingString(Context context, long drainTimeMs, String percentageString, boolean basedOnUsage) { - // round to the nearest 15 min to not appear oversly precise - final long roundedTimeMs = roundToNearestThreshold(drainTimeMs, - FIFTEEN_MINUTES_MILLIS); - CharSequence timeString = StringUtil.formatElapsedTime(context, - roundedTimeMs, - false /* withSeconds */); + // Get the time of day we think device will die rounded to the nearest 15 min. + final long roundedTimeOfDayMs = + roundToNearestThreshold( + System.currentTimeMillis() + drainTimeMs, + FIFTEEN_MINUTES_MILLIS); + + // convert the time to a properly formatted string. + DateFormat fmt = DateFormat.getTimeInstance(DateFormat.SHORT); + Date date = Date.from(Instant.ofEpochMilli(roundedTimeOfDayMs)); + CharSequence timeString = fmt.format(date); + if (TextUtils.isEmpty(percentageString)) { int id = basedOnUsage - ? R.string.power_remaining_duration_only_enhanced - : R.string.power_remaining_duration_only; + ? R.string.power_discharge_by_only_enhanced + : R.string.power_discharge_by_only; return context.getString(id, timeString); } else { int id = basedOnUsage - ? R.string.power_discharging_duration_enhanced - : R.string.power_discharging_duration; - return context.getString(id, percentageString, timeString); + ? R.string.power_discharge_by_enhanced + : R.string.power_discharge_by; + return context.getString(id, timeString, percentageString); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java index 45fdd7860836..68be2b4041b1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java +++ b/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java @@ -33,74 +33,74 @@ import java.util.Locale; /** Utility class for generally useful string methods **/ public class StringUtil { - public static final int SECONDS_PER_MINUTE = 60; - public static final int SECONDS_PER_HOUR = 60 * 60; - public static final int SECONDS_PER_DAY = 24 * 60 * 60; + public static final int SECONDS_PER_MINUTE = 60; + public static final int SECONDS_PER_HOUR = 60 * 60; + public static final int SECONDS_PER_DAY = 24 * 60 * 60; - /** - * Returns elapsed time for the given millis, in the following format: - * 2d 5h 40m 29s - * @param context the application context - * @param millis the elapsed time in milli seconds - * @param withSeconds include seconds? - * @return the formatted elapsed time - */ - public static CharSequence formatElapsedTime(Context context, double millis, - boolean withSeconds) { - SpannableStringBuilder sb = new SpannableStringBuilder(); - int seconds = (int) Math.floor(millis / 1000); - if (!withSeconds) { - // Round up. - seconds += 30; - } + /** + * Returns elapsed time for the given millis, in the following format: + * 2d 5h 40m 29s + * @param context the application context + * @param millis the elapsed time in milli seconds + * @param withSeconds include seconds? + * @return the formatted elapsed time + */ + public static CharSequence formatElapsedTime(Context context, double millis, + boolean withSeconds) { + SpannableStringBuilder sb = new SpannableStringBuilder(); + int seconds = (int) Math.floor(millis / 1000); + if (!withSeconds) { + // Round up. + seconds += 30; + } - int days = 0, hours = 0, minutes = 0; - if (seconds >= SECONDS_PER_DAY) { - days = seconds / SECONDS_PER_DAY; - seconds -= days * SECONDS_PER_DAY; - } - if (seconds >= SECONDS_PER_HOUR) { - hours = seconds / SECONDS_PER_HOUR; - seconds -= hours * SECONDS_PER_HOUR; - } - if (seconds >= SECONDS_PER_MINUTE) { - minutes = seconds / SECONDS_PER_MINUTE; - seconds -= minutes * SECONDS_PER_MINUTE; - } + int days = 0, hours = 0, minutes = 0; + if (seconds >= SECONDS_PER_DAY) { + days = seconds / SECONDS_PER_DAY; + seconds -= days * SECONDS_PER_DAY; + } + if (seconds >= SECONDS_PER_HOUR) { + hours = seconds / SECONDS_PER_HOUR; + seconds -= hours * SECONDS_PER_HOUR; + } + if (seconds >= SECONDS_PER_MINUTE) { + minutes = seconds / SECONDS_PER_MINUTE; + seconds -= minutes * SECONDS_PER_MINUTE; + } - final ArrayList<Measure> measureList = new ArrayList(4); - if (days > 0) { - measureList.add(new Measure(days, MeasureUnit.DAY)); - } - if (hours > 0) { - measureList.add(new Measure(hours, MeasureUnit.HOUR)); - } - if (minutes > 0) { - measureList.add(new Measure(minutes, MeasureUnit.MINUTE)); - } - if (withSeconds && seconds > 0) { - measureList.add(new Measure(seconds, MeasureUnit.SECOND)); - } - if (measureList.size() == 0) { - // Everything addable was zero, so nothing was added. We add a zero. - measureList.add(new Measure(0, withSeconds ? MeasureUnit.SECOND : MeasureUnit.MINUTE)); - } - final Measure[] measureArray = measureList.toArray(new Measure[measureList.size()]); + final ArrayList<Measure> measureList = new ArrayList(4); + if (days > 0) { + measureList.add(new Measure(days, MeasureUnit.DAY)); + } + if (hours > 0) { + measureList.add(new Measure(hours, MeasureUnit.HOUR)); + } + if (minutes > 0) { + measureList.add(new Measure(minutes, MeasureUnit.MINUTE)); + } + if (withSeconds && seconds > 0) { + measureList.add(new Measure(seconds, MeasureUnit.SECOND)); + } + if (measureList.size() == 0) { + // Everything addable was zero, so nothing was added. We add a zero. + measureList.add(new Measure(0, withSeconds ? MeasureUnit.SECOND : MeasureUnit.MINUTE)); + } + final Measure[] measureArray = measureList.toArray(new Measure[measureList.size()]); - final Locale locale = context.getResources().getConfiguration().locale; - final MeasureFormat measureFormat = MeasureFormat.getInstance( - locale, FormatWidth.NARROW); - sb.append(measureFormat.formatMeasures(measureArray)); + final Locale locale = context.getResources().getConfiguration().locale; + final MeasureFormat measureFormat = MeasureFormat.getInstance( + locale, FormatWidth.NARROW); + sb.append(measureFormat.formatMeasures(measureArray)); - if (measureArray.length == 1 && MeasureUnit.MINUTE.equals(measureArray[0].getUnit())) { - // Add ttsSpan if it only have minute value, because it will be read as "meters" - final TtsSpan ttsSpan = new TtsSpan.MeasureBuilder().setNumber(minutes) - .setUnit("minute").build(); - sb.setSpan(ttsSpan, 0, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } + if (measureArray.length == 1 && MeasureUnit.MINUTE.equals(measureArray[0].getUnit())) { + // Add ttsSpan if it only have minute value, because it will be read as "meters" + final TtsSpan ttsSpan = new TtsSpan.MeasureBuilder().setNumber(minutes) + .setUnit("minute").build(); + sb.setSpan(ttsSpan, 0, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } - return sb; - } + return sb; + } /** * Returns relative time for the given millis in the past, in a short format such as "2 days diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java index 9285148f7ae2..c42ff083ff11 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java @@ -24,13 +24,18 @@ import android.content.Context; import com.android.settingslib.R; import com.android.settingslib.SettingsLibRobolectricTestRunner; +import java.time.Clock; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import java.time.Duration; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowSettings.ShadowSystem; +import org.robolectric.shadows.ShadowSystemClock; @RunWith(SettingsLibRobolectricTestRunner.class) public class PowerUtilTest { @@ -39,8 +44,12 @@ public class PowerUtilTest { public static final long SEVENTEEN_MIN_MILLIS = Duration.ofMinutes(17).toMillis(); public static final long FIVE_MINUTES_MILLIS = Duration.ofMinutes(5).toMillis(); public static final long TEN_MINUTES_MILLIS = Duration.ofMinutes(10).toMillis(); - public static final long TWO_DAYS_MILLIS = Duration.ofDays(2).toMillis(); - public static final String ONE_DAY_FORMATTED = "1 day"; + public static final long THREE_DAYS_MILLIS = Duration.ofDays(3).toMillis(); + public static final long THIRTY_HOURS_MILLIS = Duration.ofHours(30).toMillis(); + public static final String TWO_DAYS_FORMATTED = "2 days"; + public static final String THIRTY_HOURS_FORMATTED = "1d 6h"; + public static final String NORMAL_CASE_EXPECTED_PREFIX = "Will last until about"; + public static final String ENHANCED_SUFFIX = "based on your usage"; private Context mContext; @@ -51,6 +60,7 @@ public class PowerUtilTest { } @Test + @Config(shadows = {ShadowSystemClock.class}) public void testGetBatteryRemainingStringFormatted_moreThanFifteenMinutes_withPercentage() { String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, SEVENTEEN_MIN_MILLIS, @@ -62,15 +72,13 @@ public class PowerUtilTest { false /* basedOnUsage */); // We only add special mention for the long string - assertThat(info).isEqualTo(mContext.getString( - R.string.power_discharging_duration_enhanced, - TEST_BATTERY_LEVEL_10, - FIFTEEN_MIN_FORMATTED)); + assertThat(info).contains(NORMAL_CASE_EXPECTED_PREFIX); + assertThat(info).contains(ENHANCED_SUFFIX); + assertThat(info).contains("%"); // shortened string should not have extra text - assertThat(info2).isEqualTo(mContext.getString( - R.string.power_discharging_duration, - TEST_BATTERY_LEVEL_10, - FIFTEEN_MIN_FORMATTED)); + assertThat(info2).contains(NORMAL_CASE_EXPECTED_PREFIX); + assertThat(info2).doesNotContain(ENHANCED_SUFFIX); + assertThat(info2).contains("%"); } @Test @@ -84,14 +92,14 @@ public class PowerUtilTest { null /* percentageString */, false /* basedOnUsage */); - // We only add special mention for the long string - assertThat(info).isEqualTo(mContext.getString( - R.string.power_remaining_duration_only_enhanced, - FIFTEEN_MIN_FORMATTED)); + // We only have % when it is provided + assertThat(info).contains(NORMAL_CASE_EXPECTED_PREFIX); + assertThat(info).contains(ENHANCED_SUFFIX); + assertThat(info).doesNotContain("%"); // shortened string should not have extra text - assertThat(info2).isEqualTo(mContext.getString( - R.string.power_remaining_duration_only, - FIFTEEN_MIN_FORMATTED)); + assertThat(info2).contains(NORMAL_CASE_EXPECTED_PREFIX); + assertThat(info2).doesNotContain(ENHANCED_SUFFIX); + assertThat(info2).doesNotContain("%"); } @@ -107,12 +115,9 @@ public class PowerUtilTest { true /* basedOnUsage */); // additional battery percentage in this string - assertThat(info).isEqualTo(mContext.getString( - R.string.power_remaining_duration_shutdown_imminent, - TEST_BATTERY_LEVEL_10)); + assertThat(info).isEqualTo("Phone may shutdown soon (10%)"); // shortened string should not have percentage - assertThat(info2).isEqualTo(mContext.getString( - R.string.power_remaining_duration_only_shutdown_imminent)); + assertThat(info2).isEqualTo("Phone may shutdown soon"); } @Test @@ -127,35 +132,42 @@ public class PowerUtilTest { true /* basedOnUsage */); // shortened string should not have percentage - assertThat(info).isEqualTo(mContext.getString( - R.string.power_remaining_less_than_duration_only, - FIFTEEN_MIN_FORMATTED)); + assertThat(info).isEqualTo("Less than 15m remaining"); // Add percentage to string when provided - assertThat(info2).isEqualTo(mContext.getString( - R.string.power_remaining_less_than_duration, - TEST_BATTERY_LEVEL_10, - FIFTEEN_MIN_FORMATTED)); + assertThat(info2).isEqualTo("Less than 15m remaining (10%)"); } @Test - public void testGetBatteryRemainingStringFormatted_moreThanOneDay_usesCorrectString() { + public void testGetBatteryRemainingStringFormatted_betweenOneAndTwoDays_usesCorrectString() { String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, - TWO_DAYS_MILLIS, + THIRTY_HOURS_MILLIS, null /* percentageString */, true /* basedOnUsage */); String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - TWO_DAYS_MILLIS, + THIRTY_HOURS_MILLIS, + TEST_BATTERY_LEVEL_10 /* percentageString */, + false /* basedOnUsage */); + + // We only add special mention for the long string + assertThat(info).isEqualTo("About 1d 6h left based on your usage"); + // shortened string should not have extra text + assertThat(info2).isEqualTo("About 1d 6h left (10%)"); + } + + @Test + public void testGetBatteryRemainingStringFormatted_moreThanTwoDays_usesCorrectString() { + String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, + THREE_DAYS_MILLIS, + null /* percentageString */, + true /* basedOnUsage */); + String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext, + THREE_DAYS_MILLIS, TEST_BATTERY_LEVEL_10 /* percentageString */, true /* basedOnUsage */); // shortened string should not have percentage - assertThat(info).isEqualTo(mContext.getString( - R.string.power_remaining_only_more_than_subtext, - ONE_DAY_FORMATTED)); + assertThat(info).isEqualTo("More than 2 days remaining"); // Add percentage to string when provided - assertThat(info2).isEqualTo(mContext.getString( - R.string.power_remaining_more_than_subtext, - TEST_BATTERY_LEVEL_10, - ONE_DAY_FORMATTED)); + assertThat(info2).isEqualTo("More than 2 days remaining (10%)"); } } diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml index 4614999e3c4f..2e7ab7fe8904 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_row.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml @@ -15,6 +15,7 @@ limitations under the License. --> +<!-- extends FrameLayout --> <com.android.systemui.statusbar.ExpandableNotificationRow xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" @@ -54,6 +55,7 @@ android:paddingStart="8dp" /> + <!-- TODO: remove --> <ImageButton android:id="@+id/helper" android:layout_width="48dp" @@ -64,7 +66,7 @@ android:tint="#FF0000" android:background="@drawable/ripple_drawable" android:visibility="visible" - /> + /> <ViewStub android:layout="@layout/notification_children_container" diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index 0103cad4a6c4..2f28c814c76b 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -17,6 +17,7 @@ package com.android.systemui.shared.system; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; +import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; @@ -403,6 +404,25 @@ public class ActivityManagerWrapper { } /** + * @return whether screen pinning is active. + */ + public boolean isScreenPinningActive() { + try { + return ActivityManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED; + } catch (RemoteException e) { + return false; + } + } + + /** + * @return whether screen pinning is enabled. + */ + public boolean isScreenPinningEnabled() { + final ContentResolver cr = AppGlobals.getInitialApplication().getContentResolver(); + return Settings.System.getInt(cr, Settings.System.LOCK_TO_APP_ENABLED, 0) != 0; + } + + /** * @return whether there is currently a locked task (ie. in screen pinning). */ public boolean isLockToAppActive() { @@ -415,9 +435,9 @@ public class ActivityManagerWrapper { /** * @return whether screen pinning is enabled. + * @deprecated See {@link #isScreenPinningEnabled} */ public boolean isLockToAppEnabled() { - final ContentResolver cr = AppGlobals.getInitialApplication().getContentResolver(); - return Settings.System.getInt(cr, Settings.System.LOCK_TO_APP_ENABLED, 0) != 0; + return isScreenPinningEnabled(); } } diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 7403ddc441f6..cad155c43867 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -44,6 +44,7 @@ import com.android.systemui.power.EnhancedEstimates; import com.android.systemui.power.EnhancedEstimatesImpl; import com.android.systemui.power.PowerNotificationWarnings; import com.android.systemui.power.PowerUI; +import com.android.systemui.statusbar.AppOpsListener; import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl; import com.android.systemui.statusbar.phone.LightBarController; @@ -314,6 +315,8 @@ public class Dependency extends SystemUI { mProviders.put(EnhancedEstimates.class, () -> new EnhancedEstimatesImpl()); + mProviders.put(AppOpsListener.class, () -> new AppOpsListener(mContext)); + // Put all dependencies above here so the factory can override them if it wants. SystemUIFactory.getInstance().injectDependencies(mProviders, mContext); } diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java index a2c9ab4871c2..5a2263cf26c7 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java @@ -14,7 +14,9 @@ package com.android.systemui; +import android.annotation.Nullable; import android.service.notification.StatusBarNotification; +import android.util.ArraySet; public interface ForegroundServiceController { /** @@ -46,4 +48,32 @@ public interface ForegroundServiceController { * @return true if sbn is the system-provided "dungeon" (list of running foreground services). */ boolean isDungeonNotification(StatusBarNotification sbn); + + /** + * @return true if sbn is one of the window manager "drawing over other apps" notifications + */ + boolean isSystemAlertNotification(StatusBarNotification sbn); + + /** + * Returns the key of the foreground service from this package using the standard template, + * if one exists. + */ + @Nullable String getStandardLayoutKey(int userId, String pkg); + + /** + * @return true if this user/pkg has a missing or custom layout notification and therefore needs + * a disclosure notification for system alert windows. + */ + boolean isSystemAlertWarningNeeded(int userId, String pkg); + + /** + * Records active app ops. App Ops are stored in FSC in addition to NotificationData in + * case they change before we have a notification to tag. + */ + void onAppOpChanged(int code, int uid, String packageName, boolean active); + + /** + * Gets active app ops for this user and package. + */ + @Nullable ArraySet<Integer> getAppOps(int userId, String packageName); } diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java index 3714c4ea7e2c..fc2b5b490e2c 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java @@ -18,13 +18,13 @@ import android.app.Notification; import android.app.NotificationManager; import android.content.Context; import android.os.Bundle; +import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto; import java.util.Arrays; @@ -34,17 +34,19 @@ import java.util.Arrays; */ public class ForegroundServiceControllerImpl implements ForegroundServiceController { - + // shelf life of foreground services before they go bad public static final long FG_SERVICE_GRACE_MILLIS = 5000; private static final String TAG = "FgServiceController"; private static final boolean DBG = false; + private final Context mContext; private final SparseArray<UserServices> mUserServices = new SparseArray<>(); private final Object mMutex = new Object(); public ForegroundServiceControllerImpl(Context context) { + mContext = context; } @Override @@ -57,6 +59,52 @@ public class ForegroundServiceControllerImpl } @Override + public boolean isSystemAlertWarningNeeded(int userId, String pkg) { + synchronized (mMutex) { + final UserServices services = mUserServices.get(userId); + if (services == null) return false; + return services.getStandardLayoutKey(pkg) == null; + } + } + + @Override + public String getStandardLayoutKey(int userId, String pkg) { + synchronized (mMutex) { + final UserServices services = mUserServices.get(userId); + if (services == null) return null; + return services.getStandardLayoutKey(pkg); + } + } + + @Override + public ArraySet<Integer> getAppOps(int userId, String pkg) { + synchronized (mMutex) { + final UserServices services = mUserServices.get(userId); + if (services == null) { + return null; + } + return services.getFeatures(pkg); + } + } + + @Override + public void onAppOpChanged(int code, int uid, String packageName, boolean active) { + int userId = UserHandle.getUserId(uid); + synchronized (mMutex) { + UserServices userServices = mUserServices.get(userId); + if (userServices == null) { + userServices = new UserServices(); + mUserServices.put(userId, userServices); + } + if (active) { + userServices.addOp(packageName, code); + } else { + userServices.removeOp(packageName, code); + } + } + } + + @Override public void addNotification(StatusBarNotification sbn, int importance) { updateNotification(sbn, importance); } @@ -102,9 +150,16 @@ public class ForegroundServiceControllerImpl } } else { userServices.removeNotification(sbn.getPackageName(), sbn.getKey()); - if (0 != (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) - && newImportance > NotificationManager.IMPORTANCE_MIN) { - userServices.addNotification(sbn.getPackageName(), sbn.getKey()); + if (0 != (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE)) { + if (newImportance > NotificationManager.IMPORTANCE_MIN) { + userServices.addImportantNotification(sbn.getPackageName(), sbn.getKey()); + } + final Notification.Builder builder = Notification.Builder.recoverBuilder( + mContext, sbn.getNotification()); + if (builder.usesStandardHeader()) { + userServices.addStandardLayoutNotification( + sbn.getPackageName(), sbn.getKey()); + } } } } @@ -117,42 +172,105 @@ public class ForegroundServiceControllerImpl && sbn.getPackageName().equals("android"); } + @Override + public boolean isSystemAlertNotification(StatusBarNotification sbn) { + // TODO: tag system alert notifications so they can be suppressed if app's notification + // is tagged + return false; + } + /** * Struct to track relevant packages and notifications for a userid's foreground services. */ private static class UserServices { private String[] mRunning = null; private long mServiceStartTime = 0; - private ArrayMap<String, ArraySet<String>> mNotifications = new ArrayMap<>(1); + // package -> sufficiently important posted notification keys + private ArrayMap<String, ArraySet<String>> mImportantNotifications = new ArrayMap<>(1); + // package -> standard layout posted notification keys + private ArrayMap<String, ArraySet<String>> mStandardLayoutNotifications = new ArrayMap<>(1); + + // package -> app ops + private ArrayMap<String, ArraySet<Integer>> mAppOps = new ArrayMap<>(1); + public void setRunningServices(String[] pkgs, long serviceStartTime) { mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null; mServiceStartTime = serviceStartTime; } - public void addNotification(String pkg, String key) { - if (mNotifications.get(pkg) == null) { - mNotifications.put(pkg, new ArraySet<String>()); + + public void addOp(String pkg, int op) { + if (mAppOps.get(pkg) == null) { + mAppOps.put(pkg, new ArraySet<>(3)); + } + mAppOps.get(pkg).add(op); + } + + public boolean removeOp(String pkg, int op) { + final boolean found; + final ArraySet<Integer> keys = mAppOps.get(pkg); + if (keys == null) { + found = false; + } else { + found = keys.remove(op); + if (keys.size() == 0) { + mAppOps.remove(pkg); + } } - mNotifications.get(pkg).add(key); + return found; } + + public void addImportantNotification(String pkg, String key) { + addNotification(mImportantNotifications, pkg, key); + } + + public boolean removeImportantNotification(String pkg, String key) { + return removeNotification(mImportantNotifications, pkg, key); + } + + public void addStandardLayoutNotification(String pkg, String key) { + addNotification(mStandardLayoutNotifications, pkg, key); + } + + public boolean removeStandardLayoutNotification(String pkg, String key) { + return removeNotification(mStandardLayoutNotifications, pkg, key); + } + public boolean removeNotification(String pkg, String key) { + boolean removed = false; + removed |= removeImportantNotification(pkg, key); + removed |= removeStandardLayoutNotification(pkg, key); + return removed; + } + + public void addNotification(ArrayMap<String, ArraySet<String>> map, String pkg, + String key) { + if (map.get(pkg) == null) { + map.put(pkg, new ArraySet<>()); + } + map.get(pkg).add(key); + } + + public boolean removeNotification(ArrayMap<String, ArraySet<String>> map, + String pkg, String key) { final boolean found; - final ArraySet<String> keys = mNotifications.get(pkg); + final ArraySet<String> keys = map.get(pkg); if (keys == null) { found = false; } else { found = keys.remove(key); if (keys.size() == 0) { - mNotifications.remove(pkg); + map.remove(pkg); } } return found; } + public boolean isDungeonNeeded() { if (mRunning != null && System.currentTimeMillis() - mServiceStartTime >= FG_SERVICE_GRACE_MILLIS) { for (String pkg : mRunning) { - final ArraySet<String> set = mNotifications.get(pkg); + final ArraySet<String> set = mImportantNotifications.get(pkg); if (set == null || set.size() == 0) { return true; } @@ -160,5 +278,27 @@ public class ForegroundServiceControllerImpl } return false; } + + public ArraySet<Integer> getFeatures(String pkg) { + return mAppOps.get(pkg); + } + + public String getStandardLayoutKey(String pkg) { + final ArraySet<String> set = mStandardLayoutNotifications.get(pkg); + if (set == null || set.size() == 0) { + return null; + } + return set.valueAt(0); + } + + @Override + public String toString() { + return "UserServices{" + + "mRunning=" + Arrays.toString(mRunning) + + ", mServiceStartTime=" + mServiceStartTime + + ", mImportantNotifications=" + mImportantNotifications + + ", mStandardLayoutNotifications=" + mStandardLayoutNotifications + + '}'; + } } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 47b0de94f133..df4a975cb0a8 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -449,7 +449,7 @@ public class Recents extends SystemUI final int activityType = runningTask != null ? runningTask.configuration.windowConfiguration.getActivityType() : ACTIVITY_TYPE_UNDEFINED; - boolean screenPinningActive = ActivityManagerWrapper.getInstance().isLockToAppActive(); + boolean screenPinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive(); boolean isRunningTaskInHomeOrRecentsStack = activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS; if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index 3f6f30bba8c4..055e72e2f8fb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -24,7 +24,6 @@ import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASO import android.app.ActivityManager; import android.app.ActivityOptions; -import android.app.KeyguardManager; import android.app.trust.TrustManager; import android.content.ActivityNotFoundException; import android.content.Context; @@ -34,7 +33,6 @@ import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; -import android.os.AsyncTask.Status; import android.os.Handler; import android.os.SystemClock; import android.util.ArraySet; @@ -385,8 +383,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener } public void toggleRecents(int growTarget) { - // Skip preloading if the task is locked - if (ActivityManagerWrapper.getInstance().isLockToAppActive()) { + if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) { return; } @@ -464,8 +461,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener } public void preloadRecents() { - // Skip preloading if the task is locked - if (ActivityManagerWrapper.getInstance().isLockToAppActive()) { + if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 3cc3273c0db4..89288d84aceb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -2188,7 +2188,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal private void readSystemFlags() { SystemServicesProxy ssp = Recents.getSystemServices(); mTouchExplorationEnabled = ssp.isTouchExplorationEnabled(); - mScreenPinningEnabled = ActivityManagerWrapper.getInstance().isLockToAppEnabled(); + mScreenPinningEnabled = ActivityManagerWrapper.getInstance().isScreenPinningEnabled() + && !ActivityManagerWrapper.getInstance().isLockToAppActive(); } private void updateStackActionButtonVisibility() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AppOpsListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/AppOpsListener.java new file mode 100644 index 000000000000..2ec78cfe9382 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AppOpsListener.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar; + +import android.app.AppOpsManager; +import android.content.Context; + +import com.android.systemui.Dependency; +import com.android.systemui.ForegroundServiceController; + +/** + * This class handles listening to notification updates and passing them along to + * NotificationPresenter to be displayed to the user. + */ +public class AppOpsListener implements AppOpsManager.OnOpActiveChangedListener { + private static final String TAG = "NotificationListener"; + + // Dependencies: + private final ForegroundServiceController mFsc = + Dependency.get(ForegroundServiceController.class); + + private final Context mContext; + protected NotificationPresenter mPresenter; + protected NotificationEntryManager mEntryManager; + protected final AppOpsManager mAppOps; + + protected static final int[] OPS = new int[] {AppOpsManager.OP_CAMERA, + AppOpsManager.OP_SYSTEM_ALERT_WINDOW, + AppOpsManager.OP_RECORD_AUDIO}; + + public AppOpsListener(Context context) { + mContext = context; + mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + } + + public void setUpWithPresenter(NotificationPresenter presenter, + NotificationEntryManager entryManager) { + mPresenter = presenter; + mEntryManager = entryManager; + mAppOps.startWatchingActive(OPS, this); + } + + public void destroy() { + mAppOps.stopWatchingActive(this); + } + + @Override + public void onOpActiveChanged(int code, int uid, String packageName, boolean active) { + mFsc.onAppOpChanged(code, uid, packageName, active); + mPresenter.getHandler().post(() -> { + mEntryManager.updateNotificationsForAppOps(code, uid, packageName, active); + }); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index bc2dff917b9a..785fc1cc5922 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -36,6 +36,7 @@ import android.os.Build; import android.os.Bundle; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; +import android.util.ArraySet; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.MathUtils; @@ -1354,6 +1355,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mHelperButton.setVisibility(show ? View.VISIBLE : View.GONE); } + public void showAppOpsIcons(ArraySet<Integer> activeOps) { + if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) { + mChildrenContainer.getHeaderView().showAppOpsIcons(activeOps); + } + mPrivateLayout.showAppOpsIcons(activeOps); + mPublicLayout.showAppOpsIcons(activeOps); + } + @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -2629,6 +2638,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mChildrenContainer = childrenContainer; } + @VisibleForTesting + protected void setPrivateLayout(NotificationContentView privateLayout) { + mPrivateLayout = privateLayout; + } + + @VisibleForTesting + protected void setPublicLayout(NotificationContentView publicLayout) { + mPublicLayout = publicLayout; + } + /** * Equivalent to View.OnLongClickListener with coordinates */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 91960df9b01d..73c87953cf45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -23,6 +23,7 @@ import android.content.Context; import android.graphics.Rect; import android.os.Build; import android.service.notification.StatusBarNotification; +import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; import android.view.NotificationHeaderView; @@ -1423,6 +1424,17 @@ public class NotificationContentView extends FrameLayout { return header; } + public void showAppOpsIcons(ArraySet<Integer> activeOps) { + if (mContractedChild != null && mContractedWrapper.getNotificationHeader() != null) { + mContractedWrapper.getNotificationHeader().showAppOpsIcons(activeOps); + } + if (mExpandedChild != null && mExpandedWrapper.getNotificationHeader() != null) { + mExpandedWrapper.getNotificationHeader().showAppOpsIcons(activeOps); + } + if (mHeadsUpChild != null && mHeadsUpWrapper.getNotificationHeader() != null) { + mHeadsUpWrapper.getNotificationHeader().showAppOpsIcons(activeOps); + } + } public NotificationHeaderView getContractedNotificationHeader() { if (mContractedChild != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 127f3f918fba..d53cb03cfcb7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar; import android.app.AppGlobals; +import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -34,6 +35,7 @@ import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; +import android.util.ArraySet; import android.view.View; import android.widget.ImageView; import android.widget.RemoteViews; @@ -65,6 +67,8 @@ public class NotificationData { private final Environment mEnvironment; private HeadsUpManager mHeadsUpManager; + final ForegroundServiceController mFsc = Dependency.get(ForegroundServiceController.class); + public static final class Entry { private static final long LAUNCH_COOLDOWN = 2000; private static final long REMOTE_INPUT_COOLDOWN = 500; @@ -95,6 +99,7 @@ public class NotificationData { private Throwable mDebugThrowable; public CharSequence remoteInputTextWhenReset; public long lastRemoteInputSent = NOT_LAUNCHED_YET; + public ArraySet<Integer> mActiveAppOps = new ArraySet<>(3); public Entry(StatusBarNotification n) { this.key = n.getKey(); @@ -194,7 +199,7 @@ public class NotificationData { /** * Update the notification icons. * @param context the context to create the icons with. - * @param n the notification to read the icon from. + * @param sbn the notification to read the icon from. * @throws InflationException */ public void updateIcons(Context context, StatusBarNotification sbn) @@ -375,6 +380,8 @@ public class NotificationData { } mGroupManager.onEntryAdded(entry); + updateAppOps(entry); + updateRankingAndSort(mRankingMap); } @@ -393,6 +400,35 @@ public class NotificationData { updateRankingAndSort(ranking); } + private void updateAppOps(Entry entry) { + final int uid = entry.notification.getUid(); + final String pkg = entry.notification.getPackageName(); + ArraySet<Integer> activeOps = mFsc.getAppOps(entry.notification.getUserId(), pkg); + if (activeOps != null) { + int N = activeOps.size(); + for (int i = 0; i < N; i++) { + updateAppOp(activeOps.valueAt(i), uid, pkg, true); + } + } + } + + public void updateAppOp(int appOp, int uid, String pkg, boolean showIcon) { + synchronized (mEntries) { + final int N = mEntries.size(); + for (int i = 0; i < N; i++) { + Entry entry = mEntries.valueAt(i); + if (uid == entry.notification.getUid() + && pkg.equals(entry.notification.getPackageName())) { + if (showIcon) { + entry.mActiveAppOps.add(appOp); + } else { + entry.mActiveAppOps.remove(appOp); + } + } + } + } + } + public boolean isAmbient(String key) { if (mRankingMap != null) { getRanking(key, mTmpRanking); @@ -545,11 +581,14 @@ public class NotificationData { return true; } - final ForegroundServiceController fsc = Dependency.get(ForegroundServiceController.class); - if (fsc.isDungeonNotification(sbn) && !fsc.isDungeonNeededForUser(sbn.getUserId())) { + if (mFsc.isDungeonNotification(sbn) && !mFsc.isDungeonNeededForUser(sbn.getUserId())) { // this is a foreground-service disclosure for a user that does not need to show one return true; } + if (mFsc.isSystemAlertNotification(sbn) && !mFsc.isSystemAlertWarningNeeded( + sbn.getUserId(), sbn.getPackageName())) { + return true; + } return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java index 7360486ac7e9..71f7911b41f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java @@ -31,6 +31,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.UserHandle; import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationStats; @@ -77,7 +78,7 @@ import java.util.List; public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback, ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler, VisualStabilityManager.Callback { - private static final String TAG = "NotificationEntryManager"; + private static final String TAG = "NotificationEntryMgr"; protected static final boolean DEBUG = false; protected static final boolean ENABLE_HEADS_UP = true; protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; @@ -734,6 +735,14 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. } } + public void updateNotificationsForAppOps(int appOp, int uid, String pkg, boolean showIcon) { + if (mForegroundServiceController.getStandardLayoutKey( + UserHandle.getUserId(uid), pkg) != null) { + mNotificationData.updateAppOp(appOp, uid, pkg, showIcon); + updateNotifications(); + } + } + private boolean alertAgain(NotificationData.Entry oldEntry, Notification newNotification) { return oldEntry == null || !oldEntry.hasInterrupted() || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index cd4c7ae8d57e..75b8b371119e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -342,6 +342,8 @@ public class NotificationViewHierarchyManager { row.showBlockingHelper(entry.userSentiment == NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE); + + row.showAppOpsIcons(entry.mActiveAppOps); } mPresenter.onUpdateRowStates(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index aba5cdf0ca2b..d2cdc27d982c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.phone; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.res.Configuration; import android.content.res.Resources; import android.support.v4.util.ArraySet; import android.util.Log; @@ -32,6 +33,7 @@ import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; @@ -45,12 +47,12 @@ import java.util.Stack; */ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback, - OnHeadsUpChangedListener { + OnHeadsUpChangedListener, ConfigurationController.ConfigurationListener { private static final String TAG = "HeadsUpManagerPhone"; private static final boolean DEBUG = false; private final View mStatusBarWindowView; - private final int mStatusBarHeight; + private int mStatusBarHeight; private final NotificationGroupManager mGroupManager; private final StatusBar mBar; private final VisualStabilityManager mVisualStabilityManager; @@ -291,6 +293,13 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, } } + @Override + public void onConfigChanged(Configuration newConfig) { + Resources resources = mContext.getResources(); + mStatusBarHeight = resources.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height); + } + /////////////////////////////////////////////////////////////////////////////////////////////// // VisualStabilityManager.Callback overrides: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 62151cfa258b..0ed69e66b03e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -708,7 +708,8 @@ public class NavigationBarFragment extends Fragment implements Callbacks { @VisibleForTesting boolean onHomeLongClick(View v) { - if (!mNavigationBarView.isRecentsButtonVisible() && mNavigationBarView.inScreenPinning()) { + if (!mNavigationBarView.isRecentsButtonVisible() + && ActivityManagerWrapper.getInstance().isScreenPinningActive()) { return onLongPressBackHome(v); } if (shouldDisableNavbarGestures()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java index 320b56f98c85..a4daed92cabf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java @@ -43,6 +43,7 @@ import com.android.systemui.RecentsComponent; import com.android.systemui.SysUiServiceProvider; import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; import com.android.systemui.shared.recents.IOverviewProxy; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.stackdivider.Divider; import com.android.systemui.tuner.TunerService; @@ -149,7 +150,8 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture } public boolean onInterceptTouchEvent(MotionEvent event) { - if (mNavigationBarView.inScreenPinning() || mStatusBar.isKeyguardShowing()) { + if (ActivityManagerWrapper.getInstance().isScreenPinningActive() + || mStatusBar.isKeyguardShowing()) { return false; } @@ -182,7 +184,8 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture } public boolean onTouchEvent(MotionEvent event) { - if (mNavigationBarView.inScreenPinning() || mStatusBar.isKeyguardShowing()) { + if (ActivityManagerWrapper.getInstance().isScreenPinningActive() + || mStatusBar.isKeyguardShowing()) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index a5621e5a4010..74fbed1b0da7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -21,8 +21,6 @@ import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME; import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.LayoutTransition; import android.animation.LayoutTransition.TransitionListener; import android.animation.ObjectAnimator; @@ -30,7 +28,6 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.DrawableRes; import android.annotation.StyleRes; -import android.app.ActivityManager; import android.app.StatusBarManager; import android.content.Context; import android.content.res.Configuration; @@ -41,7 +38,6 @@ import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Message; -import android.os.RemoteException; import android.os.SystemProperties; import android.support.annotation.ColorInt; import android.util.AttributeSet; @@ -60,7 +56,6 @@ import android.widget.FrameLayout; import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.DockedStackExistsListener; -import com.android.systemui.Interpolators; import com.android.systemui.OverviewProxyService; import com.android.systemui.R; import com.android.systemui.RecentsComponent; @@ -379,15 +374,20 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav return getRecentsButton().getVisibility() == View.VISIBLE; } + public boolean isOverviewEnabled() { + return (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) == 0; + } + public boolean isQuickStepSwipeUpEnabled() { return mOverviewProxyService.getProxy() != null + && isOverviewEnabled() && ((mOverviewProxyService.getInteractionFlags() & FLAG_DISABLE_SWIPE_UP) == 0); } public boolean isQuickScrubEnabled() { return SystemProperties.getBoolean("persist.quickstep.scrub.enabled", true) - && mOverviewProxyService.getProxy() != null && !isRecentsButtonVisible() + && mOverviewProxyService.getProxy() != null && isOverviewEnabled() && ((mOverviewProxyService.getInteractionFlags() & FLAG_DISABLE_QUICK_SCRUB) == 0); } @@ -575,8 +575,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav boolean disableHome = ((disabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0); // Always disable recents when alternate car mode UI is active. - boolean disableRecent = mUseCarModeUi - || ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0); + boolean disableRecent = mUseCarModeUi || !isOverviewEnabled(); boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) && ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0); @@ -584,17 +583,18 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav // When screen pinning, don't hide back and home when connected service or back and // recents buttons when disconnected from launcher service in screen pinning mode, // as they are used for exiting. + final boolean pinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive(); if (mOverviewProxyService.getProxy() != null) { // Use interaction flags to show/hide navigation buttons but will be shown if required // to exit screen pinning. final int flags = mOverviewProxyService.getInteractionFlags(); disableRecent |= (flags & FLAG_SHOW_OVERVIEW_BUTTON) == 0; - if (inScreenPinning()) { + if (pinningActive) { disableBack = disableHome = false; } else { disableBack |= (flags & FLAG_HIDE_BACK_BUTTON) != 0; } - } else if (inScreenPinning()) { + } else if (pinningActive) { disableBack = disableRecent = false; } @@ -614,7 +614,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } public boolean inScreenPinning() { - return ActivityManagerWrapper.getInstance().isLockToAppActive(); + return ActivityManagerWrapper.getInstance().isScreenPinningActive(); } public void setLayoutTransitionsEnabled(boolean enabled) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index cc5a93ce71b6..900ec0be4b54 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -264,7 +264,7 @@ public class PhoneStatusBarView extends PanelBar { mScrimController.setPanelExpansion(scrimFraction); } - public void onDensityOrFontScaleChanged() { + public void updateResources() { ViewGroup.LayoutParams layoutParams = getLayoutParams(); layoutParams.height = getResources().getDimensionPixelSize( R.dimen.status_bar_height); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index a31727e67078..86e618e4690c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -180,6 +180,7 @@ import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.stackdivider.Divider; import com.android.systemui.stackdivider.WindowManagerProxy; import com.android.systemui.statusbar.ActivatableNotificationView; +import com.android.systemui.statusbar.AppOpsListener; import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CrossFadeHelper; @@ -405,6 +406,7 @@ public class StatusBar extends SystemUI implements DemoMode, protected NotificationLogger mNotificationLogger; protected NotificationEntryManager mEntryManager; protected NotificationViewHierarchyManager mViewHierarchyManager; + protected AppOpsListener mAppOpsListener; /** * Helper that is responsible for showing the right toast when a disallowed activity operation @@ -622,6 +624,8 @@ public class StatusBar extends SystemUI implements DemoMode, mMediaManager = Dependency.get(NotificationMediaManager.class); mEntryManager = Dependency.get(NotificationEntryManager.class); mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class); + mAppOpsListener = Dependency.get(AppOpsListener.class); + mAppOpsListener.setUpWithPresenter(this, mEntryManager); mColorExtractor = Dependency.get(SysuiColorExtractor.class); mColorExtractor.addOnColorsChangedListener(this); @@ -813,6 +817,7 @@ public class StatusBar extends SystemUI implements DemoMode, mHeadsUpManager = new HeadsUpManagerPhone(context, mStatusBarWindow, mGroupManager, this, mVisualStabilityManager); + Dependency.get(ConfigurationController.class).addCallback(mHeadsUpManager); mHeadsUpManager.addListener(this); mHeadsUpManager.addListener(mNotificationPanel); mHeadsUpManager.addListener(mGroupManager); @@ -1069,7 +1074,6 @@ public class StatusBar extends SystemUI implements DemoMode, // end old BaseStatusBar.onDensityOrFontScaleChanged(). mScrimController.onDensityOrFontScaleChanged(); // TODO: Remove this. - if (mStatusBarView != null) mStatusBarView.onDensityOrFontScaleChanged(); if (mBrightnessMirrorController != null) { mBrightnessMirrorController.onDensityOrFontScaleChanged(); } @@ -3079,6 +3083,9 @@ public class StatusBar extends SystemUI implements DemoMode, loadDimens(); + if (mStatusBarView != null) { + mStatusBarView.updateResources(); + } if (mNotificationPanel != null) { mNotificationPanel.updateResources(); } @@ -3293,6 +3300,7 @@ public class StatusBar extends SystemUI implements DemoMode, Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(null); mDeviceProvisionedController.removeCallback(mUserSetupObserver); Dependency.get(ConfigurationController.class).removeCallback(this); + mAppOpsListener.destroy(); } private boolean mDemoModeAllowed; @@ -4519,7 +4527,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (isScreenTurningOnOrOn()) { if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Launching camera"); if (mStatusBarKeyguardViewManager.isBouncerShowing()) { - mStatusBarKeyguardViewManager.hideBouncer(false /* destroyView */); + mStatusBarKeyguardViewManager.reset(true /* hide */); } mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source); updateScrimController(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 49cffc090a51..a009d80dad4f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -164,7 +164,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb updateStates(); } - public void hideBouncer(boolean destroyView) { + private void hideBouncer(boolean destroyView) { mBouncer.hide(destroyView); cancelPendingWakeupAction(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 1b55a5b0325f..66fde7986b00 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -1394,6 +1394,7 @@ public class NotificationStackScrollLayout extends ViewGroup @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height); float densityScale = getResources().getDisplayMetrics().density; mSwipeHelper.setDensityScale(densityScale); float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java index 943020c7b28e..18dd3c734660 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java @@ -16,6 +16,14 @@ package com.android.systemui; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNull; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import android.annotation.UserIdInt; import android.app.Notification; import android.app.NotificationManager; @@ -24,17 +32,14 @@ import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.widget.RemoteViews; + import com.android.internal.messages.nano.SystemMessageProto; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - @SmallTest @RunWith(AndroidJUnit4.class) public class ForegroundServiceControllerTest extends SysuiTestCase { @@ -49,7 +54,7 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { } @Test - public void testNotificationCRUD() { + public void testNotificationCRUD_dungeon() { StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, "com.example.app1"); StatusBarNotification sbn_user2_app2_fg = makeMockFgSBN(USERID_TWO, "com.example.app2"); StatusBarNotification sbn_user1_app3_fg = makeMockFgSBN(USERID_ONE, "com.example.app3"); @@ -98,6 +103,101 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { } @Test + public void testNotificationCRUD_stdLayout() { + StatusBarNotification sbn_user1_app1_fg = + makeMockFgSBN(USERID_ONE, "com.example.app1", 0, true); + StatusBarNotification sbn_user2_app2_fg = + makeMockFgSBN(USERID_TWO, "com.example.app2", 1, true); + StatusBarNotification sbn_user1_app3_fg = + makeMockFgSBN(USERID_ONE, "com.example.app3", 2, true); + StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1", + 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); + StatusBarNotification sbn_user2_app1 = makeMockSBN(USERID_TWO, "com.example.app1", + 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); + + assertFalse(fsc.removeNotification(sbn_user1_app3_fg)); + assertFalse(fsc.removeNotification(sbn_user2_app2_fg)); + assertFalse(fsc.removeNotification(sbn_user1_app1_fg)); + assertFalse(fsc.removeNotification(sbn_user1_app1)); + assertFalse(fsc.removeNotification(sbn_user2_app1)); + + fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN); + fsc.addNotification(sbn_user2_app2_fg, NotificationManager.IMPORTANCE_MIN); + fsc.addNotification(sbn_user1_app3_fg, NotificationManager.IMPORTANCE_MIN); + fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN); + fsc.addNotification(sbn_user2_app1, NotificationManager.IMPORTANCE_MIN); + + // these are never added to the tracker + assertFalse(fsc.removeNotification(sbn_user1_app1)); + assertFalse(fsc.removeNotification(sbn_user2_app1)); + + fsc.updateNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN); + fsc.updateNotification(sbn_user2_app1, NotificationManager.IMPORTANCE_MIN); + // should still not be there + assertFalse(fsc.removeNotification(sbn_user1_app1)); + assertFalse(fsc.removeNotification(sbn_user2_app1)); + + fsc.updateNotification(sbn_user2_app2_fg, NotificationManager.IMPORTANCE_MIN); + fsc.updateNotification(sbn_user1_app3_fg, NotificationManager.IMPORTANCE_MIN); + fsc.updateNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN); + + assertTrue(fsc.removeNotification(sbn_user1_app3_fg)); + assertFalse(fsc.removeNotification(sbn_user1_app3_fg)); + + assertTrue(fsc.removeNotification(sbn_user2_app2_fg)); + assertFalse(fsc.removeNotification(sbn_user2_app2_fg)); + + assertTrue(fsc.removeNotification(sbn_user1_app1_fg)); + assertFalse(fsc.removeNotification(sbn_user1_app1_fg)); + + assertFalse(fsc.removeNotification(sbn_user1_app1)); + assertFalse(fsc.removeNotification(sbn_user2_app1)); + } + + @Test + public void testAppOpsCRUD() { + // no crash on remove that doesn't exist + fsc.onAppOpChanged(9, 1000, "pkg1", false); + assertNull(fsc.getAppOps(0, "pkg1")); + + // multiuser & multipackage + fsc.onAppOpChanged(8, 50, "pkg1", true); + fsc.onAppOpChanged(1, 60, "pkg3", true); + fsc.onAppOpChanged(7, 500000, "pkg2", true); + + assertEquals(1, fsc.getAppOps(0, "pkg1").size()); + assertTrue(fsc.getAppOps(0, "pkg1").contains(8)); + + assertEquals(1, fsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size()); + assertTrue(fsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7)); + + assertEquals(1, fsc.getAppOps(0, "pkg3").size()); + assertTrue(fsc.getAppOps(0, "pkg3").contains(1)); + + // multiple ops for the same package + fsc.onAppOpChanged(9, 50, "pkg1", true); + fsc.onAppOpChanged(5, 50, "pkg1", true); + + assertEquals(3, fsc.getAppOps(0, "pkg1").size()); + assertTrue(fsc.getAppOps(0, "pkg1").contains(8)); + assertTrue(fsc.getAppOps(0, "pkg1").contains(9)); + assertTrue(fsc.getAppOps(0, "pkg1").contains(5)); + + assertEquals(1, fsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size()); + assertTrue(fsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7)); + + // remove one of the multiples + fsc.onAppOpChanged(9, 50, "pkg1", false); + assertEquals(2, fsc.getAppOps(0, "pkg1").size()); + assertTrue(fsc.getAppOps(0, "pkg1").contains(8)); + assertTrue(fsc.getAppOps(0, "pkg1").contains(5)); + + // remove last op + fsc.onAppOpChanged(1, 60, "pkg3", false); + assertNull(fsc.getAppOps(0, "pkg3")); + } + + @Test public void testDungeonPredicate() { StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1", 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); @@ -252,6 +352,14 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); + // importance upgrade + fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN); + assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); + assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE; + fsc.updateNotification(sbn_user1_app1_fg, + NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification + // finally, let's turn off the service fsc.addNotification(makeMockDungeon(USERID_ONE, null), NotificationManager.IMPORTANCE_DEFAULT); @@ -260,12 +368,71 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); } + @Test + public void testStdLayoutBasic() { + final String PKG1 = "com.example.app0"; + + StatusBarNotification sbn_user1_app1 = makeMockFgSBN(USERID_ONE, PKG1, 0, true); + sbn_user1_app1.getNotification().flags = 0; + StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1, 1, true); + fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN); // not fg + assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required! + fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN); + assertFalse(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // app1 has got it covered + assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "otherpkg")); + // let's take out the non-fg notification and see what happens. + fsc.removeNotification(sbn_user1_app1); + // still covered by sbn_user1_app1_fg + assertFalse(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); + assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "anyPkg")); + + // let's attempt to downgrade the notification from FLAG_FOREGROUND and see what we get + StatusBarNotification sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1, 1, true); + sbn_user1_app1_fg_sneaky.getNotification().flags = 0; + fsc.updateNotification(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_MIN); + assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required! + assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "anything")); + // ok, ok, we'll put it back + sbn_user1_app1_fg_sneaky.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; + fsc.updateNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN); + assertFalse(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); + assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "whatever")); + + assertTrue(fsc.removeNotification(sbn_user1_app1_fg_sneaky)); + assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required! + assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "a")); + + // let's try a custom layout + sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1, 1, false); + fsc.updateNotification(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_MIN); + assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required! + assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "anything")); + // now let's test an upgrade (non fg to fg) + fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN); + assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); + assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "b")); + sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE; + fsc.updateNotification(sbn_user1_app1, + NotificationManager.IMPORTANCE_MIN); // this is now a fg notification + + assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, PKG1)); + assertFalse(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); + + // remove it, make sure we're out of compliance again + assertTrue(fsc.removeNotification(sbn_user1_app1)); // was fg, should return true + assertFalse(fsc.removeNotification(sbn_user1_app1)); + assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, PKG1)); + assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); + } + private StatusBarNotification makeMockSBN(int userid, String pkg, int id, String tag, int flags) { final Notification n = mock(Notification.class); + n.extras = new Bundle(); n.flags = flags; return makeMockSBN(userid, pkg, id, tag, n); } + private StatusBarNotification makeMockSBN(int userid, String pkg, int id, String tag, Notification n) { final StatusBarNotification sbn = mock(StatusBarNotification.class); @@ -278,9 +445,25 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { when(sbn.getKey()).thenReturn("MOCK:"+userid+"|"+pkg+"|"+id+"|"+tag); return sbn; } + + private StatusBarNotification makeMockFgSBN(int userid, String pkg, int id, + boolean usesStdLayout) { + StatusBarNotification sbn = + makeMockSBN(userid, pkg, id, "foo", Notification.FLAG_FOREGROUND_SERVICE); + if (usesStdLayout) { + sbn.getNotification().contentView = null; + sbn.getNotification().headsUpContentView = null; + sbn.getNotification().bigContentView = null; + } else { + sbn.getNotification().contentView = mock(RemoteViews.class); + } + return sbn; + } + private StatusBarNotification makeMockFgSBN(int userid, String pkg) { return makeMockSBN(userid, pkg, 1000, "foo", Notification.FLAG_FOREGROUND_SERVICE); } + private StatusBarNotification makeMockDungeon(int userid, String[] pkgs) { final Notification n = mock(Notification.class); n.flags = Notification.FLAG_ONGOING_EVENT; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AppOpsListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AppOpsListenerTest.java new file mode 100644 index 000000000000..2a48c4b67e0e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AppOpsListenerTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AppOpsManager; +import android.os.Handler; +import android.os.Looper; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.systemui.ForegroundServiceController; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class AppOpsListenerTest extends SysuiTestCase { + private static final String TEST_PACKAGE_NAME = "test"; + private static final int TEST_UID = 0; + + @Mock private NotificationPresenter mPresenter; + @Mock private AppOpsManager mAppOpsManager; + + // Dependency mocks: + @Mock private NotificationEntryManager mEntryManager; + @Mock private ForegroundServiceController mFsc; + + private AppOpsListener mListener; + private Handler mHandler; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); + mDependency.injectTestDependency(ForegroundServiceController.class, mFsc); + getContext().addMockSystemService(AppOpsManager.class, mAppOpsManager); + mHandler = new Handler(Looper.getMainLooper()); + when(mPresenter.getHandler()).thenReturn(mHandler); + + mListener = new AppOpsListener(mContext); + } + + @Test + public void testOnlyListenForFewOps() { + mListener.setUpWithPresenter(mPresenter, mEntryManager); + + verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsListener.OPS, mListener); + } + + @Test + public void testStopListening() { + mListener.destroy(); + verify(mAppOpsManager, times(1)).stopWatchingActive(mListener); + } + + @Test + public void testInformEntryMgrOnAppOpsChange() { + mListener.setUpWithPresenter(mPresenter, mEntryManager); + mListener.onOpActiveChanged( + AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); + waitForIdleSync(mHandler); + verify(mEntryManager, times(1)).updateNotificationsForAppOps( + AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); + } + + @Test + public void testInformFscOnAppOpsChange() { + mListener.setUpWithPresenter(mPresenter, mEntryManager); + mListener.onOpActiveChanged( + AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); + waitForIdleSync(mHandler); + verify(mFsc, times(1)).onAppOpChanged( + AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java index 544585a4a917..ce629bb41e7b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java @@ -19,10 +19,15 @@ package com.android.systemui.statusbar; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.app.AppOpsManager; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.util.ArraySet; +import android.view.NotificationHeaderView; import android.view.View; import com.android.systemui.SysuiTestCase; @@ -146,4 +151,34 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { Assert.assertTrue("Should always play sounds when not trusted.", mGroup.isSoundEffectsEnabled()); } + + @Test + public void testShowAppOpsIcons_noHeader() { + // public notification is custom layout - no header + mGroup.setSensitive(true, true); + mGroup.showAppOpsIcons(new ArraySet<>()); + } + + @Test + public void testShowAppOpsIcons_header() throws Exception { + NotificationHeaderView mockHeader = mock(NotificationHeaderView.class); + + NotificationContentView publicLayout = mock(NotificationContentView.class); + mGroup.setPublicLayout(publicLayout); + NotificationContentView privateLayout = mock(NotificationContentView.class); + mGroup.setPrivateLayout(privateLayout); + NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class); + when(mockContainer.getNotificationChildCount()).thenReturn(1); + when(mockContainer.getHeaderView()).thenReturn(mockHeader); + mGroup.setChildrenContainer(mockContainer); + + ArraySet<Integer> ops = new ArraySet<>(); + ops.add(AppOpsManager.OP_ANSWER_PHONE_CALLS); + mGroup.showAppOpsIcons(ops); + + verify(mockHeader, times(1)).showAppOpsIcons(ops); + verify(privateLayout, times(1)).showAppOpsIcons(ops); + verify(publicLayout, times(1)).showAppOpsIcons(ops); + + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java index 436849c9d700..1fb4c371a408 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java @@ -16,14 +16,23 @@ package com.android.systemui.statusbar; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.app.AppOpsManager; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.util.ArraySet; +import android.view.NotificationHeaderView; import android.view.View; import com.android.systemui.SysuiTestCase; @@ -75,4 +84,35 @@ public class NotificationContentViewTest extends SysuiTestCase { mView.setHeadsUpAnimatingAway(true); Assert.assertFalse(mView.isAnimatingVisibleType()); } + + @Test + @UiThreadTest + public void testShowAppOpsIcons() { + NotificationHeaderView mockContracted = mock(NotificationHeaderView.class); + when(mockContracted.findViewById(com.android.internal.R.id.notification_header)) + .thenReturn(mockContracted); + NotificationHeaderView mockExpanded = mock(NotificationHeaderView.class); + when(mockExpanded.findViewById(com.android.internal.R.id.notification_header)) + .thenReturn(mockExpanded); + NotificationHeaderView mockHeadsUp = mock(NotificationHeaderView.class); + when(mockHeadsUp.findViewById(com.android.internal.R.id.notification_header)) + .thenReturn(mockHeadsUp); + NotificationHeaderView mockAmbient = mock(NotificationHeaderView.class); + when(mockAmbient.findViewById(com.android.internal.R.id.notification_header)) + .thenReturn(mockAmbient); + + mView.setContractedChild(mockContracted); + mView.setExpandedChild(mockExpanded); + mView.setHeadsUpChild(mockHeadsUp); + mView.setAmbientChild(mockAmbient); + + ArraySet<Integer> ops = new ArraySet<>(); + ops.add(AppOpsManager.OP_ANSWER_PHONE_CALLS); + mView.showAppOpsIcons(ops); + + verify(mockContracted, times(1)).showAppOpsIcons(ops); + verify(mockExpanded, times(1)).showAppOpsIcons(ops); + verify(mockAmbient, never()).showAppOpsIcons(ops); + verify(mockHeadsUp, times(1)).showAppOpsIcons(any()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java index 972eddb46901..b1e1c02a035f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java @@ -16,8 +16,16 @@ package com.android.systemui.statusbar; +import static android.app.AppOpsManager.OP_ACCEPT_HANDOVER; +import static android.app.AppOpsManager.OP_CAMERA; + +import static junit.framework.Assert.assertEquals; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -33,7 +41,9 @@ import android.service.notification.StatusBarNotification; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.util.ArraySet; +import com.android.systemui.ForegroundServiceController; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.phone.NotificationGroupManager; @@ -41,6 +51,8 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidJUnit4.class) @@ -51,6 +63,10 @@ public class NotificationDataTest extends SysuiTestCase { private final StatusBarNotification mMockStatusBarNotification = mock(StatusBarNotification.class); + @Mock + ForegroundServiceController mFsc; + @Mock + NotificationData.Environment mEnvironment; private final IPackageManager mMockPackageManager = mock(IPackageManager.class); private NotificationData mNotificationData; @@ -58,6 +74,7 @@ public class NotificationDataTest extends SysuiTestCase { @Before public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); when(mMockStatusBarNotification.getUid()).thenReturn(UID_NORMAL); when(mMockPackageManager.checkUidPermission( @@ -69,9 +86,11 @@ public class NotificationDataTest extends SysuiTestCase { eq(UID_ALLOW_DURING_SETUP))) .thenReturn(PackageManager.PERMISSION_GRANTED); - NotificationData.Environment mock = mock(NotificationData.Environment.class); - when(mock.getGroupManager()).thenReturn(new NotificationGroupManager()); - mNotificationData = new TestableNotificationData(mock); + mDependency.injectTestDependency(ForegroundServiceController.class, mFsc); + when(mEnvironment.getGroupManager()).thenReturn(new NotificationGroupManager()); + when(mEnvironment.isDeviceProvisioned()).thenReturn(true); + when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true); + mNotificationData = new TestableNotificationData(mEnvironment); mNotificationData.updateRanking(mock(NotificationListenerService.RankingMap.class)); mRow = new NotificationTestHelper(getContext()).createRow(); } @@ -117,6 +136,117 @@ public class NotificationDataTest extends SysuiTestCase { Assert.assertTrue(mRow.getEntry().channel != null); } + @Test + public void testAdd_appOpsAdded() { + ArraySet<Integer> expected = new ArraySet<>(); + expected.add(3); + expected.add(235); + expected.add(1); + when(mFsc.getAppOps(mRow.getEntry().notification.getUserId(), + mRow.getEntry().notification.getPackageName())).thenReturn(expected); + + mNotificationData.add(mRow.getEntry()); + assertEquals(expected.size(), + mNotificationData.get(mRow.getEntry().key).mActiveAppOps.size()); + for (int op : expected) { + assertTrue(" entry missing op " + op, + mNotificationData.get(mRow.getEntry().key).mActiveAppOps.contains(op)); + } + } + + @Test + public void testAdd_noExistingAppOps() { + when(mFsc.getAppOps(mRow.getEntry().notification.getUserId(), + mRow.getEntry().notification.getPackageName())).thenReturn(null); + + mNotificationData.add(mRow.getEntry()); + assertEquals(0, mNotificationData.get(mRow.getEntry().key).mActiveAppOps.size()); + } + + @Test + public void testAllRelevantNotisTaggedWithAppOps() throws Exception { + mNotificationData.add(mRow.getEntry()); + ExpandableNotificationRow row2 = new NotificationTestHelper(getContext()).createRow(); + mNotificationData.add(row2.getEntry()); + ExpandableNotificationRow diffPkg = + new NotificationTestHelper(getContext()).createRow("pkg", 4000); + mNotificationData.add(diffPkg.getEntry()); + + ArraySet<Integer> expectedOps = new ArraySet<>(); + expectedOps.add(OP_CAMERA); + expectedOps.add(OP_ACCEPT_HANDOVER); + + for (int op : expectedOps) { + mNotificationData.updateAppOp(op, NotificationTestHelper.UID, + NotificationTestHelper.PKG, true); + } + for (int op : expectedOps) { + assertTrue(mRow.getEntry().key + " doesn't have op " + op, + mNotificationData.get(mRow.getEntry().key).mActiveAppOps.contains(op)); + assertTrue(row2.getEntry().key + " doesn't have op " + op, + mNotificationData.get(row2.getEntry().key).mActiveAppOps.contains(op)); + assertFalse(diffPkg.getEntry().key + " has op " + op, + mNotificationData.get(diffPkg.getEntry().key).mActiveAppOps.contains(op)); + } + } + + @Test + public void testAppOpsRemoval() throws Exception { + mNotificationData.add(mRow.getEntry()); + ExpandableNotificationRow row2 = new NotificationTestHelper(getContext()).createRow(); + mNotificationData.add(row2.getEntry()); + + ArraySet<Integer> expectedOps = new ArraySet<>(); + expectedOps.add(OP_CAMERA); + expectedOps.add(OP_ACCEPT_HANDOVER); + + for (int op : expectedOps) { + mNotificationData.updateAppOp(op, NotificationTestHelper.UID, + NotificationTestHelper.PKG, true); + } + + expectedOps.remove(OP_ACCEPT_HANDOVER); + mNotificationData.updateAppOp(OP_ACCEPT_HANDOVER, NotificationTestHelper.UID, + NotificationTestHelper.PKG, false); + + assertTrue(mRow.getEntry().key + " doesn't have op " + OP_CAMERA, + mNotificationData.get(mRow.getEntry().key).mActiveAppOps.contains(OP_CAMERA)); + assertTrue(row2.getEntry().key + " doesn't have op " + OP_CAMERA, + mNotificationData.get(row2.getEntry().key).mActiveAppOps.contains(OP_CAMERA)); + assertFalse(mRow.getEntry().key + " has op " + OP_ACCEPT_HANDOVER, + mNotificationData.get(mRow.getEntry().key) + .mActiveAppOps.contains(OP_ACCEPT_HANDOVER)); + assertFalse(row2.getEntry().key + " has op " + OP_ACCEPT_HANDOVER, + mNotificationData.get(row2.getEntry().key) + .mActiveAppOps.contains(OP_ACCEPT_HANDOVER)); + } + + @Test + public void testSuppressSystemAlertNotification() { + when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false); + when(mFsc.isSystemAlertNotification(any())).thenReturn(true); + + assertTrue(mNotificationData.shouldFilterOut(mRow.getEntry().notification)); + } + + @Test + public void testDoNotSuppressSystemAlertNotification() { + when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true); + when(mFsc.isSystemAlertNotification(any())).thenReturn(true); + + assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry().notification)); + + when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true); + when(mFsc.isSystemAlertNotification(any())).thenReturn(false); + + assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry().notification)); + + when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false); + when(mFsc.isSystemAlertNotification(any())).thenReturn(false); + + assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry().notification)); + } + private void initStatusBarNotification(boolean allowDuringSetup) { Bundle bundle = new Bundle(); bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java index f9ec3f92181f..37dd939ea70a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java @@ -23,14 +23,17 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationManager; import android.content.Context; @@ -274,4 +277,40 @@ public class NotificationEntryManagerTest extends SysuiTestCase { assertNull(mEntryManager.getNotificationData().get(mSbn.getKey())); } + + @Test + public void testUpdateAppOps_foregroundNoti() { + com.android.systemui.util.Assert.isNotMainThread(); + + when(mForegroundServiceController.getStandardLayoutKey(anyInt(), anyString())) + .thenReturn("something"); + mEntry.row = mRow; + mEntryManager.getNotificationData().add(mEntry); + + + mHandler.post(() -> { + mEntryManager.updateNotificationsForAppOps( + AppOpsManager.OP_CAMERA, mEntry.notification.getUid(), + mEntry.notification.getPackageName(), true); + }); + waitForIdleSync(mHandler); + + verify(mPresenter, times(1)).updateNotificationViews(); + assertTrue(mEntryManager.getNotificationData().get(mEntry.key).mActiveAppOps.contains( + AppOpsManager.OP_CAMERA)); + } + + @Test + public void testUpdateAppOps_otherNoti() { + com.android.systemui.util.Assert.isNotMainThread(); + + when(mForegroundServiceController.getStandardLayoutKey(anyInt(), anyString())) + .thenReturn(null); + mHandler.post(() -> { + mEntryManager.updateNotificationsForAppOps(AppOpsManager.OP_CAMERA, 1000, "pkg", true); + }); + waitForIdleSync(mHandler); + + verify(mPresenter, never()).updateNotificationViews(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java index f3c1171f650c..27642544c129 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -48,6 +48,8 @@ public class NotificationTestHelper { private ExpandableNotificationRow mRow; private InflationException mException; private HeadsUpManager mHeadsUpManager; + protected static final String PKG = "com.android.systemui"; + protected static final int UID = 1000; public NotificationTestHelper(Context context) { mContext = context; @@ -55,7 +57,7 @@ public class NotificationTestHelper { mHeadsUpManager = new HeadsUpManagerPhone(mContext, null, mGroupManager, null, null); } - public ExpandableNotificationRow createRow() throws Exception { + public ExpandableNotificationRow createRow(String pkg, int uid) throws Exception { Notification publicVersion = new Notification.Builder(mContext).setSmallIcon( R.drawable.ic_person) .setCustomContentView(new RemoteViews(mContext.getPackageName(), @@ -67,10 +69,19 @@ public class NotificationTestHelper { .setContentText("Text") .setPublicVersion(publicVersion) .build(); - return createRow(notification); + return createRow(notification, pkg, uid); + } + + public ExpandableNotificationRow createRow() throws Exception { + return createRow(PKG, UID); } public ExpandableNotificationRow createRow(Notification notification) throws Exception { + return createRow(notification, PKG, UID); + } + + public ExpandableNotificationRow createRow(Notification notification, String pkg, int uid) + throws Exception { LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( mContext.LAYOUT_INFLATER_SERVICE); mInstrumentation.runOnMainSync(() -> { @@ -83,8 +94,7 @@ public class NotificationTestHelper { row.setHeadsUpManager(mHeadsUpManager); row.setAboveShelfChangedListener(aboveShelf -> {}); UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser()); - StatusBarNotification sbn = new StatusBarNotification("com.android.systemui", - "com.android.systemui", mId++, null, 1000, + StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, mId++, null, uid, 2000, notification, mUser, null, System.currentTimeMillis()); NotificationData.Entry entry = new NotificationData.Entry(sbn); entry.row = row; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java index fbe730a64c6f..76ed45206dff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -19,6 +19,9 @@ package com.android.systemui.statusbar; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -170,6 +173,19 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { assertEquals(View.VISIBLE, entry1.row.getVisibility()); } + @Test + public void testUpdateNotificationViews_appOps() throws Exception { + NotificationData.Entry entry0 = createEntry(); + entry0.row = spy(entry0.row); + when(mNotificationData.getActiveNotifications()).thenReturn( + Lists.newArrayList(entry0)); + mListContainer.addContainerView(entry0.row); + + mViewHierarchyManager.updateNotificationViews(); + + verify(entry0.row, times(1)).showAppOpsIcons(any()); + } + private class FakeListContainer implements NotificationListContainer { final LinearLayout mLayout = new LinearLayout(mContext); final List<View> mRows = Lists.newArrayList(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 31442af5a04c..ff545f0bd653 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -69,6 +69,7 @@ import com.android.systemui.assist.AssistManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.statusbar.ActivatableNotificationView; +import com.android.systemui.statusbar.AppOpsListener; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationData; @@ -145,6 +146,7 @@ public class StatusBarTest extends SysuiTestCase { mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager); mDependency.injectTestDependency(NotificationListener.class, mNotificationListener); mDependency.injectTestDependency(KeyguardMonitor.class, mock(KeyguardMonitorImpl.class)); + mDependency.injectTestDependency(AppOpsListener.class, mock(AppOpsListener.class)); mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class)); mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class)); diff --git a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values/config.xml index a584a7f3fb90..c22b2e778ff1 100644 --- a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values/config.xml +++ b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values/config.xml @@ -46,7 +46,8 @@ <bool name="config_fillMainBuiltInDisplayCutout">true</bool> <!-- Height of the status bar --> - <dimen name="status_bar_height">48dp</dimen> + <dimen name="status_bar_height_portrait">48dp</dimen> + <dimen name="status_bar_height_landscape">28dp</dimen> <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) --> <dimen name="quick_qs_offset_height">48dp</dimen> <!-- Total height of QQS (quick_qs_offset_height + 128) --> diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values/config.xml index 915e16412155..401e09211ae7 100644 --- a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values/config.xml +++ b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values/config.xml @@ -46,7 +46,8 @@ <bool name="config_fillMainBuiltInDisplayCutout">true</bool> <!-- Height of the status bar --> - <dimen name="status_bar_height">48dp</dimen> + <dimen name="status_bar_height_portrait">48dp</dimen> + <dimen name="status_bar_height_landscape">28dp</dimen> <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) --> <dimen name="quick_qs_offset_height">48dp</dimen> <!-- Total height of QQS (quick_qs_offset_height + 128) --> diff --git a/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values/config.xml index b8e29da8c8e7..f328b83c1cbf 100644 --- a/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values/config.xml +++ b/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values/config.xml @@ -46,7 +46,8 @@ <bool name="config_fillMainBuiltInDisplayCutout">true</bool> <!-- Height of the status bar --> - <dimen name="status_bar_height">48dp</dimen> + <dimen name="status_bar_height_portrait">48dp</dimen> + <dimen name="status_bar_height_landscape">28dp</dimen> <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) --> <dimen name="quick_qs_offset_height">48dp</dimen> <!-- Total height of QQS (quick_qs_offset_height + 128) --> diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index b897c7cc8873..6f31b0a22445 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -5343,6 +5343,11 @@ message MetricsEvent { // OS: P ACTION_BATTERY_TIP_SHOWN = 1324; + // OPEN: Settings > Security & Location > Location > See all + // CATEGORY: SETTINGS + // OS: P + RECENT_LOCATION_REQUESTS_ALL = 1325; + // ---- End P Constants, all P constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 9842aa94219d..28a79bdb79c8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -12881,6 +12881,25 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public boolean isBackgroundRestricted(String packageName) { + final int callingUid = Binder.getCallingUid(); + final IPackageManager pm = AppGlobals.getPackageManager(); + try { + final int packageUid = pm.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING, + UserHandle.getUserId(callingUid)); + if (packageUid != callingUid) { + throw new IllegalArgumentException("Uid " + callingUid + + " cannot query restriction state for package " + packageName); + } + } catch (RemoteException exc) { + // Ignore. + } + final int mode = mAppOpsService.checkOperation(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, + callingUid, packageName); + return (mode != AppOpsManager.MODE_ALLOWED); + } + + @Override public void backgroundWhitelistUid(final int uid) { if (Binder.getCallingUid() != Process.SYSTEM_UID) { throw new SecurityException("Only the OS may call backgroundWhitelistUid()"); diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 3d7c9d207f38..81dae394c792 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -51,6 +51,7 @@ import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManager; +import android.opengl.GLES10; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -82,16 +83,18 @@ import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; +import java.util.HashSet; import java.util.List; -import java.util.Map; +import java.util.Set; import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.opengles.GL; -import javax.microedition.khronos.opengles.GL10; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; import static android.app.ActivityManager.RESIZE_MODE_SYSTEM; import static android.app.ActivityManager.RESIZE_MODE_USER; @@ -1858,6 +1861,137 @@ final class ActivityManagerShellCommand extends ShellCommand { } } + /** + * Adds all supported GL extensions for a provided EGLConfig to a set by creating an EGLContext + * and EGLSurface and querying extensions. + * + * @param egl An EGL API object + * @param display An EGLDisplay to create a context and surface with + * @param config The EGLConfig to get the extensions for + * @param surfaceSize eglCreatePbufferSurface generic parameters + * @param contextAttribs eglCreateContext generic parameters + * @param glExtensions A Set<String> to add GL extensions to + */ + private static void addExtensionsForConfig( + EGL10 egl, + EGLDisplay display, + EGLConfig config, + int[] surfaceSize, + int[] contextAttribs, + Set<String> glExtensions) { + // Create a context. + EGLContext context = + egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, contextAttribs); + // No-op if we can't create a context. + if (context == EGL10.EGL_NO_CONTEXT) { + return; + } + + // Create a surface. + EGLSurface surface = egl.eglCreatePbufferSurface(display, config, surfaceSize); + if (surface == EGL10.EGL_NO_SURFACE) { + egl.eglDestroyContext(display, context); + return; + } + + // Update the current surface and context. + egl.eglMakeCurrent(display, surface, surface, context); + + // Get the list of extensions. + String extensionList = GLES10.glGetString(GLES10.GL_EXTENSIONS); + if (!TextUtils.isEmpty(extensionList)) { + // The list of extensions comes from the driver separated by spaces. + // Split them apart and add them into a Set for deduping purposes. + for (String extension : extensionList.split(" ")) { + glExtensions.add(extension); + } + } + + // Tear down the context and surface for this config. + egl.eglMakeCurrent(display, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + egl.eglDestroySurface(display, surface); + egl.eglDestroyContext(display, context); + } + + + Set<String> getGlExtensionsFromDriver() { + Set<String> glExtensions = new HashSet<>(); + + // Get the EGL implementation. + EGL10 egl = (EGL10) EGLContext.getEGL(); + if (egl == null) { + getErrPrintWriter().println("Warning: couldn't get EGL"); + return glExtensions; + } + + // Get the default display and initialize it. + EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + int[] version = new int[2]; + egl.eglInitialize(display, version); + + // Call getConfigs() in order to find out how many there are. + int[] numConfigs = new int[1]; + if (!egl.eglGetConfigs(display, null, 0, numConfigs)) { + getErrPrintWriter().println("Warning: couldn't get EGL config count"); + return glExtensions; + } + + // Allocate space for all configs and ask again. + EGLConfig[] configs = new EGLConfig[numConfigs[0]]; + if (!egl.eglGetConfigs(display, configs, numConfigs[0], numConfigs)) { + getErrPrintWriter().println("Warning: couldn't get EGL configs"); + return glExtensions; + } + + // Allocate surface size parameters outside of the main loop to cut down + // on GC thrashing. 1x1 is enough since we are only using it to get at + // the list of extensions. + int[] surfaceSize = + new int[] { + EGL10.EGL_WIDTH, 1, + EGL10.EGL_HEIGHT, 1, + EGL10.EGL_NONE + }; + + // For when we need to create a GLES2.0 context. + final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + int[] gles2 = new int[] {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE}; + + // For getting return values from eglGetConfigAttrib + int[] attrib = new int[1]; + + for (int i = 0; i < numConfigs[0]; i++) { + // Get caveat for this config in order to skip slow (i.e. software) configs. + egl.eglGetConfigAttrib(display, configs[i], EGL10.EGL_CONFIG_CAVEAT, attrib); + if (attrib[0] == EGL10.EGL_SLOW_CONFIG) { + continue; + } + + // If the config does not support pbuffers we cannot do an eglMakeCurrent + // on it in addExtensionsForConfig(), so skip it here. Attempting to make + // it current with a pbuffer will result in an EGL_BAD_MATCH error + egl.eglGetConfigAttrib(display, configs[i], EGL10.EGL_SURFACE_TYPE, attrib); + if ((attrib[0] & EGL10.EGL_PBUFFER_BIT) == 0) { + continue; + } + + final int EGL_OPENGL_ES_BIT = 0x0001; + final int EGL_OPENGL_ES2_BIT = 0x0004; + egl.eglGetConfigAttrib(display, configs[i], EGL10.EGL_RENDERABLE_TYPE, attrib); + if ((attrib[0] & EGL_OPENGL_ES_BIT) != 0) { + addExtensionsForConfig(egl, display, configs[i], surfaceSize, null, glExtensions); + } + if ((attrib[0] & EGL_OPENGL_ES2_BIT) != 0) { + addExtensionsForConfig(egl, display, configs[i], surfaceSize, gles2, glExtensions); + } + } + + // Release all EGL resources. + egl.eglTerminate(display); + + return glExtensions; + } + private void writeDeviceConfig(ProtoOutputStream protoOutputStream, long fieldId, PrintWriter pw, Configuration config, DisplayManager dm) { Point stableSize = dm.getStableDisplaySize(); @@ -1906,18 +2040,24 @@ final class ActivityManagerShellCommand extends ShellCommand { } } - /* - GL10 gl = ((GL10)((EGL10)EGLContext.getEGL()).eglGetCurrentContext().getGL()); - protoOutputStream.write(DeviceConfigurationProto.OPENGL_VERSION, - gl.glGetString(GL10.GL_VERSION)); - String glExtensions = gl.glGetString(GL10.GL_EXTENSIONS); - for (String ext : glExtensions.split(" ")) { - protoOutputStream.write(DeviceConfigurationProto.OPENGL_EXTENSIONS, ext); + Set<String> glExtensionsSet = getGlExtensionsFromDriver(); + String[] glExtensions = new String[glExtensionsSet.size()]; + glExtensions = glExtensionsSet.toArray(glExtensions); + Arrays.sort(glExtensions); + for (int i = 0; i < glExtensions.length; i++) { + if (protoOutputStream != null) { + protoOutputStream.write(DeviceConfigurationProto.OPENGL_EXTENSIONS, + glExtensions[i]); + } + if (pw != null) { + pw.print("opengl-extensions: "); pw.println(glExtensions[i]); + } + } - */ PackageManager pm = mInternal.mContext.getPackageManager(); List<SharedLibraryInfo> slibs = pm.getSharedLibraries(0); + Collections.sort(slibs, Comparator.comparing(SharedLibraryInfo::getName)); for (int i = 0; i < slibs.size(); i++) { if (protoOutputStream != null) { protoOutputStream.write(DeviceConfigurationProto.SHARED_LIBRARIES, @@ -1929,6 +2069,8 @@ final class ActivityManagerShellCommand extends ShellCommand { } FeatureInfo[] features = pm.getSystemAvailableFeatures(); + Arrays.sort(features, (o1, o2) -> + (o1.name == o2.name ? 0 : (o1.name == null ? -1 : o1.name.compareTo(o2.name)))); for (int i = 0; i < features.length; i++) { if (features[i].name != null) { if (protoOutputStream != null) { diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java index 978e344b22ff..5d5ed550a00c 100644 --- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java @@ -51,6 +51,7 @@ import android.util.SparseIntArray; import android.util.StatsLog; import com.android.internal.logging.MetricsLogger; +import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; import com.android.server.LocalServices; @@ -74,8 +75,6 @@ class ActivityMetricsLogger { private static final long INVALID_START_TIME = -1; private static final int MSG_CHECK_VISIBILITY = 0; - private static final int MSG_LOG_APP_TRANSITION = 1; - private static final int MSG_LOG_APP_START_MEMORY_STATE_CAPTURE = 2; // Preallocated strings we are sending to tron, so we don't have to allocate a new one every // time we log. @@ -116,13 +115,6 @@ class ActivityMetricsLogger { final SomeArgs args = (SomeArgs) msg.obj; checkVisibility((TaskRecord) args.arg1, (ActivityRecord) args.arg2); break; - case MSG_LOG_APP_TRANSITION: - logAppTransition(msg.arg1, msg.arg2, - (WindowingModeTransitionInfoSnapshot) msg.obj); - break; - case MSG_LOG_APP_START_MEMORY_STATE_CAPTURE: - logAppStartMemoryStateCapture((WindowingModeTransitionInfo) msg.obj); - break; } } } @@ -141,11 +133,13 @@ class ActivityMetricsLogger { private final class WindowingModeTransitionInfoSnapshot { final private ApplicationInfo applicationInfo; + final private ProcessRecord processRecord; final private String packageName; final private String launchedActivityName; final private String launchedActivityLaunchedFromPackage; final private String launchedActivityLaunchToken; final private String launchedActivityAppRecordRequiredAbi; + final private String processName; final private int reason; final private int startingWindowDelayMs; final private int bindApplicationDelayMs; @@ -166,6 +160,8 @@ class ActivityMetricsLogger { bindApplicationDelayMs = info.bindApplicationDelayMs; windowsDrawnDelayMs = info.windowsDrawnDelayMs; type = getTransitionType(info); + processRecord = findProcessForActivity(info.launchedActivity); + processName = info.launchedActivity.processName; } } @@ -505,15 +501,16 @@ class ActivityMetricsLogger { // This will avoid any races with other operations that modify the ActivityRecord. final WindowingModeTransitionInfoSnapshot infoSnapshot = new WindowingModeTransitionInfoSnapshot(info); - mHandler.obtainMessage(MSG_LOG_APP_TRANSITION, mCurrentTransitionDeviceUptime, - mCurrentTransitionDelayMs, infoSnapshot).sendToTarget(); + final int currentTransitionDeviceUptime = mCurrentTransitionDeviceUptime; + final int currentTransitionDelayMs = mCurrentTransitionDelayMs; + BackgroundThread.getHandler().post(() -> logAppTransition( + currentTransitionDeviceUptime, currentTransitionDelayMs, infoSnapshot)); info.launchedActivity.info.launchToken = null; - mHandler.obtainMessage(MSG_LOG_APP_START_MEMORY_STATE_CAPTURE, info).sendToTarget(); } } - // This gets called on the handler without holding the activity manager lock. + // This gets called on a background thread without holding the activity manager lock. private void logAppTransition(int currentTransitionDeviceUptime, int currentTransitionDelayMs, WindowingModeTransitionInfoSnapshot info) { final LogMaker builder = new LogMaker(APP_TRANSITION); @@ -572,6 +569,7 @@ class ActivityMetricsLogger { launchToken, packageOptimizationInfo.getCompilationReason(), packageOptimizationInfo.getCompilationFilter()); + logAppStartMemoryStateCapture(info); } private int convertAppStartTransitionType(int tronType) { @@ -629,15 +627,14 @@ class ActivityMetricsLogger { return -1; } - private void logAppStartMemoryStateCapture(WindowingModeTransitionInfo info) { - final ProcessRecord processRecord = findProcessForActivity(info.launchedActivity); - if (processRecord == null) { + private void logAppStartMemoryStateCapture(WindowingModeTransitionInfoSnapshot info) { + if (info.processRecord == null) { if (DEBUG_METRICS) Slog.i(TAG, "logAppStartMemoryStateCapture processRecord null"); return; } - final int pid = processRecord.pid; - final int uid = info.launchedActivity.appInfo.uid; + final int pid = info.processRecord.pid; + final int uid = info.applicationInfo.uid; final MemoryStat memoryStat = readMemoryStatFromMemcg(uid, pid); if (memoryStat == null) { if (DEBUG_METRICS) Slog.i(TAG, "logAppStartMemoryStateCapture memoryStat null"); @@ -647,8 +644,8 @@ class ActivityMetricsLogger { StatsLog.write( StatsLog.APP_START_MEMORY_STATE_CAPTURED, uid, - info.launchedActivity.processName, - info.launchedActivity.info.name, + info.processName, + info.launchedActivityName, memoryStat.pgfault, memoryStat.pgmajfault, memoryStat.rssInBytes, diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 2f6afd2318e5..728c07d02d46 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -109,6 +109,7 @@ import android.app.ResultInfo; import android.app.WindowConfiguration.ActivityType; import android.app.WindowConfiguration.WindowingMode; import android.app.servertransaction.ActivityResultItem; +import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.NewIntentItem; import android.app.servertransaction.WindowVisibilityItem; import android.app.servertransaction.DestroyActivityItem; @@ -2609,6 +2610,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } try { + final ClientTransaction transaction = ClientTransaction.obtain(next.app.thread, + next.appToken); // Deliver all pending results. ArrayList<ResultInfo> a = next.results; if (a != null) { @@ -2616,15 +2619,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (!next.finishing && N > 0) { if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a); - mService.mLifecycleManager.scheduleTransaction(next.app.thread, - next.appToken, ActivityResultItem.obtain(a)); + transaction.addCallback(ActivityResultItem.obtain(a)); } } if (next.newIntents != null) { - mService.mLifecycleManager.scheduleTransaction(next.app.thread, - next.appToken, NewIntentItem.obtain(next.newIntents, - false /* andPause */)); + transaction.addCallback(NewIntentItem.obtain(next.newIntents, + false /* andPause */)); } // Well the app will no longer be stopped. @@ -2641,11 +2642,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai next.app.pendingUiClean = true; next.app.forceProcessStateUpTo(mService.mTopProcessState); next.clearOptionsLocked(); - mService.mLifecycleManager.scheduleTransaction(next.app.thread, next.appToken, + + transaction.setLifecycleStateRequest( ResumeActivityItem.obtain(next.app.repProcState, mService.isNextTransitionForward()) .setDescription(next.getLifecycleDescription( "resumeTopActivityInnerLocked"))); + mService.mLifecycleManager.scheduleTransaction(transaction); if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed " + next); diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java index 524de91b694e..df60c6654c4b 100644 --- a/services/core/java/com/android/server/display/BrightnessTracker.java +++ b/services/core/java/com/android/server/display/BrightnessTracker.java @@ -70,6 +70,8 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.TimeUnit; /** @@ -225,15 +227,24 @@ public class BrightnessTracker { * @return List of recent {@link BrightnessChangeEvent}s */ public ParceledListSlice<BrightnessChangeEvent> getEvents(int userId, boolean includePackage) { - // TODO include apps from any managed profiles in the brightness information. BrightnessChangeEvent[] events; synchronized (mEventsLock) { events = mEvents.toArray(); } + int[] profiles = mInjector.getProfileIds(mUserManager, userId); + Map<Integer, Boolean> toRedact = new HashMap<>(); + for (int i = 0; i < profiles.length; ++i) { + int profileId = profiles[i]; + // Include slider interactions when a managed profile app is in the + // foreground but always redact the package name. + boolean redact = (!includePackage) || profileId != userId; + toRedact.put(profiles[i], redact); + } ArrayList<BrightnessChangeEvent> out = new ArrayList<>(events.length); for (int i = 0; i < events.length; ++i) { - if (events[i].userId == userId) { - if (includePackage) { + Boolean redact = toRedact.get(events[i].userId); + if (redact != null) { + if (!redact) { out.add(events[i]); } else { BrightnessChangeEvent event = new BrightnessChangeEvent((events[i]), @@ -817,6 +828,14 @@ public class BrightnessTracker { return userManager.getUserHandle(userSerialNumber); } + public int[] getProfileIds(UserManager userManager, int userId) { + if (userManager != null) { + return userManager.getProfileIds(userId, false); + } else { + return new int[]{userId}; + } + } + public ActivityManager.StackInfo getFocusedStack() throws RemoteException { return ActivityManager.getService().getFocusedStackInfo(); } diff --git a/services/core/java/com/android/server/media/MediaUpdateService.java b/services/core/java/com/android/server/media/MediaUpdateService.java index 6921ccde250c..f38b35342f3a 100644 --- a/services/core/java/com/android/server/media/MediaUpdateService.java +++ b/services/core/java/com/android/server/media/MediaUpdateService.java @@ -16,27 +16,21 @@ package com.android.server.media; -import android.content.Context; -import android.content.Intent; -import android.media.IMediaResourceMonitor; -import android.os.Binder; -import android.os.UserHandle; -import android.os.UserManager; -import android.util.Log; -import android.util.Slog; -import com.android.server.SystemService; - import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.media.IMediaExtractorUpdateService; import android.os.IBinder; +import android.os.Handler; import android.os.PatternMatcher; import android.os.ServiceManager; -import android.media.IMediaExtractorUpdateService; - -import java.lang.Exception; +import android.os.UserHandle; +import android.util.Log; +import android.util.Slog; +import com.android.server.SystemService; /** This class provides a system service that manages media framework updates. */ public class MediaUpdateService extends SystemService { @@ -46,9 +40,11 @@ public class MediaUpdateService extends SystemService { private static final String EXTRACTOR_UPDATE_SERVICE_NAME = "media.extractor.update"; private IMediaExtractorUpdateService mMediaExtractorUpdateService; + final Handler mHandler; public MediaUpdateService(Context context) { super(context); + mHandler = new Handler(); } @Override @@ -77,7 +73,12 @@ public class MediaUpdateService extends SystemService { } if (binder != null) { mMediaExtractorUpdateService = IMediaExtractorUpdateService.Stub.asInterface(binder); - packageStateChanged(); + mHandler.post(new Runnable() { + @Override + public void run() { + packageStateChanged(); + } + }); } else { Slog.w(TAG, EXTRACTOR_UPDATE_SERVICE_NAME + " not found."); } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 7efc9876993b..2f1fbf9c5fb4 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -485,7 +485,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mSafeMode; private final ArraySet<WindowState> mScreenDecorWindows = new ArraySet<>(); WindowState mStatusBar = null; - int mStatusBarHeight; + private final int[] mStatusBarHeightForRotation = new int[4]; WindowState mNavigationBar = null; boolean mHasNavigationBar = false; boolean mNavigationBarCanMove = false; // can the navigation bar ever move to the side? @@ -2768,8 +2768,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { Context uiContext = getSystemUiContext(); final Resources res = uiContext.getResources(); - mStatusBarHeight = - res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); + mStatusBarHeightForRotation[mPortraitRotation] = + mStatusBarHeightForRotation[mUpsideDownRotation] = res.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height_portrait); + mStatusBarHeightForRotation[mLandscapeRotation] = + mStatusBarHeightForRotation[mSeascapeRotation] = res.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height_landscape); // Height of the navigation bar when presented horizontally at bottom mNavigationBarHeightForRotationDefault[mPortraitRotation] = @@ -2884,11 +2888,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { // of the screen. // TODO(multi-display): Support status bars on secondary displays. if (displayId == DEFAULT_DISPLAY) { - int statusBarHeight = mStatusBarHeight; + int statusBarHeight = mStatusBarHeightForRotation[rotation]; if (displayCutout != null) { // If there is a cutout, it may already have accounted for some part of the status // bar height. - statusBarHeight = Math.max(0, mStatusBarHeight - displayCutout.getSafeInsetTop()); + statusBarHeight = Math.max(0, statusBarHeight - displayCutout.getSafeInsetTop()); } return getNonDecorDisplayHeight(fullWidth, fullHeight, rotation, uiMode, displayId, displayCutout) - statusBarHeight; @@ -4649,7 +4653,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { displayFrames.mDisplayCutout); // For layout, the status bar is always at the top with our fixed height. - displayFrames.mStable.top = displayFrames.mUnrestricted.top + mStatusBarHeight; + displayFrames.mStable.top = displayFrames.mUnrestricted.top + + mStatusBarHeightForRotation[displayFrames.mRotation]; boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0; boolean statusBarTranslucent = (sysui @@ -6938,7 +6943,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Navigation bar and status bar. getNonDecorInsetsLw(displayRotation, displayWidth, displayHeight, displayCutout, outInsets); - outInsets.top = Math.max(outInsets.top, mStatusBarHeight); + outInsets.top = Math.max(outInsets.top, mStatusBarHeightForRotation[displayRotation]); } @Override diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java index fb1595e1eb49..a527e17b393b 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java @@ -111,8 +111,6 @@ public class ActivityRecordTests extends ActivityTestsBase { assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 0); } - // TODO: b/71582913 - @Ignore("b/71582913") @Test public void testPausingWhenVisibleFromStopped() throws Exception { final MutableBoolean pauseFound = new MutableBoolean(false); diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java index 501f9666f5c4..b4f84742301d 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java @@ -338,6 +338,26 @@ public class BrightnessTrackerTest { assertFalse(event.isDefaultBrightnessConfig); assertEquals(1.0, event.powerBrightnessFactor, FLOAT_DELTA); assertFalse(event.isUserSetBrightness); + + // Pretend user 1 is a profile of user 0. + mInjector.mProfiles = new int[]{0, 1}; + events = tracker.getEvents(0, true).getList(); + // Both events should now be returned. + assertEquals(2, events.size()); + BrightnessChangeEvent userZeroEvent; + BrightnessChangeEvent userOneEvent; + if (events.get(0).userId == 0) { + userZeroEvent = events.get(0); + userOneEvent = events.get(1); + } else { + userZeroEvent = events.get(1); + userOneEvent = events.get(0); + } + assertEquals(0, userZeroEvent.userId); + assertEquals("com.example.app", userZeroEvent.packageName); + assertEquals(1, userOneEvent.userId); + // Events from user 1 should have the package name redacted + assertNull(userOneEvent.packageName); } @Test @@ -597,6 +617,7 @@ public class BrightnessTrackerTest { Handler mHandler; boolean mIdleScheduled; boolean mInteractive = true; + int[] mProfiles; public TestInjector(Handler handler) { mHandler = handler; @@ -682,6 +703,15 @@ public class BrightnessTrackerTest { } @Override + public int[] getProfileIds(UserManager userManager, int userId) { + if (mProfiles != null) { + return mProfiles; + } else { + return new int[]{userId}; + } + } + + @Override public ActivityManager.StackInfo getFocusedStack() throws RemoteException { ActivityManager.StackInfo focusedStack = new ActivityManager.StackInfo(); focusedStack.userId = 0; @@ -689,15 +719,18 @@ public class BrightnessTrackerTest { return focusedStack; } + @Override public void scheduleIdleJob(Context context) { // Don't actually schedule jobs during unit tests. mIdleScheduled = true; } + @Override public void cancelIdleJob(Context context) { mIdleScheduled = false; } + @Override public boolean isInteractive(Context context) { return mInteractive; } diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java index 30ca9caf56de..1d4348c0b6d4 100644 --- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java +++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java @@ -77,7 +77,9 @@ public class PhoneWindowManagerTestBase { public void setUpBase() throws Exception { mContext = new TestContextWrapper(InstrumentationRegistry.getTargetContext()); mContext.getResourceMocker().addOverride( - com.android.internal.R.dimen.status_bar_height, STATUS_BAR_HEIGHT); + com.android.internal.R.dimen.status_bar_height_portrait, STATUS_BAR_HEIGHT); + mContext.getResourceMocker().addOverride( + com.android.internal.R.dimen.status_bar_height_landscape, STATUS_BAR_HEIGHT); mContext.getResourceMocker().addOverride( com.android.internal.R.dimen.navigation_bar_height, NAV_BAR_HEIGHT); mContext.getResourceMocker().addOverride( diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java index 06a5c2e2710b..8529a89a9914 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java @@ -27,6 +27,7 @@ import android.net.ConnectivityManager; import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -420,9 +421,12 @@ public class WifiAwareManager { "createNetworkSpecifier: Invalid 'role' argument when creating a network " + "specifier"); } - if (peerHandle == null) { - throw new IllegalArgumentException( - "createNetworkSpecifier: Invalid peer handle - cannot be null"); + if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR || !WifiAwareUtils.isLegacyVersion(mContext, + Build.VERSION_CODES.P)) { + if (peerHandle == null) { + throw new IllegalArgumentException( + "createNetworkSpecifier: Invalid peer handle - cannot be null"); + } } return new WifiAwareNetworkSpecifier( @@ -453,9 +457,12 @@ public class WifiAwareManager { "createNetworkSpecifier: Invalid 'role' argument when creating a network " + "specifier"); } - if (peer == null) { - throw new IllegalArgumentException( - "createNetworkSpecifier: Invalid peer MAC - cannot be null"); + if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR || !WifiAwareUtils.isLegacyVersion(mContext, + Build.VERSION_CODES.P)) { + if (peer == null) { + throw new IllegalArgumentException( + "createNetworkSpecifier: Invalid peer MAC - cannot be null"); + } } if (peer != null && peer.length != 6) { throw new IllegalArgumentException("createNetworkSpecifier: Invalid peer MAC address"); diff --git a/wifi/java/android/net/wifi/aware/WifiAwareUtils.java b/wifi/java/android/net/wifi/aware/WifiAwareUtils.java index fda7a9abc318..3ece93dfdf0a 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareUtils.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareUtils.java @@ -16,6 +16,8 @@ package android.net.wifi.aware; +import android.content.Context; +import android.content.pm.PackageManager; import android.hardware.wifi.V1_0.Constants; /** @@ -84,4 +86,21 @@ public class WifiAwareUtils { return true; } + + /** + * Returns true if the App version is older than minVersion. + */ + public static boolean isLegacyVersion(Context context, int minVersion) { + try { + if (context.getPackageManager().getApplicationInfo(context.getOpPackageName(), 0) + .targetSdkVersion < minVersion) { + return true; + } + } catch (PackageManager.NameNotFoundException e) { + // In case of exception, assume known app (more strict checking) + // Note: This case will never happen since checkPackage is + // called to verify valididity before checking App's version. + } + return false; + } } diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java index 84e3ed9d2c44..272f72721747 100644 --- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java +++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java @@ -19,14 +19,20 @@ package android.net.wifi.aware; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.net.wifi.RttManager; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Parcel; @@ -79,12 +85,32 @@ public class WifiAwareManagerTest { @Mock public RttManager.RttListener mockRttListener; + @Mock + public PackageManager mockPackageManager; + + @Mock + public ApplicationInfo mockApplicationInfo; + private static final int AWARE_STATUS_ERROR = -1; + private static final byte[] PMK_VALID = "01234567890123456789012345678901".getBytes(); + private static final byte[] PMK_INVALID = "012".getBytes(); + + private static final String PASSPHRASE_VALID = "SomeLongEnoughPassphrase"; + private static final String PASSPHRASE_TOO_SHORT = "012"; + private static final String PASSPHRASE_TOO_LONG = + "0123456789012345678901234567890123456789012345678901234567890123456789"; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.P; + when(mockPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn( + mockApplicationInfo); + when(mockContext.getOpPackageName()).thenReturn("XXX"); + when(mockContext.getPackageManager()).thenReturn(mockPackageManager); + mDut = new WifiAwareManager(mockContext, mockAwareService); mMockLooper = new TestLooper(); mMockLooperHandler = new Handler(mMockLooper.getLooper()); @@ -884,8 +910,8 @@ public class WifiAwareManagerTest { final int sessionId = 123; final PeerHandle peerHandle = new PeerHandle(123412); final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER; - final byte[] pmk = "01234567890123456789012345678901".getBytes(); - final String passphrase = "A really bad password"; + final byte[] pmk = PMK_VALID; + final String passphrase = PASSPHRASE_VALID; final ConfigRequest configRequest = new ConfigRequest.Builder().build(); final PublishConfig publishConfig = new PublishConfig.Builder().build(); @@ -965,8 +991,8 @@ public class WifiAwareManagerTest { final ConfigRequest configRequest = new ConfigRequest.Builder().build(); final byte[] someMac = HexEncoding.decode("000102030405".toCharArray(), false); final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR; - final byte[] pmk = "01234567890123456789012345678901".getBytes(); - final String passphrase = "A really bad password"; + final byte[] pmk = PMK_VALID; + final String passphrase = PASSPHRASE_VALID; ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass( WifiAwareSession.class); @@ -1030,7 +1056,7 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierWithClientIncorrectLengthPmk() throws Exception { - executeNetworkSpecifierWithClient(new PeerHandle(123412), true, "012".getBytes(), null); + executeNetworkSpecifierWithClient(new PeerHandle(123412), true, PMK_INVALID, null); } /** @@ -1045,17 +1071,17 @@ public class WifiAwareManagerTest { * Validate that a too short Passphrase triggers an exception. */ @Test(expected = IllegalArgumentException.class) - public void testNetworkSpecifierWithClientShortPassphrase() throws Exception { - executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, "012"); + public void testNetworkSpecifierWithClientTooShortPassphrase() throws Exception { + executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, + PASSPHRASE_TOO_SHORT); } /** * Validate that a too long Passphrase triggers an exception. */ @Test(expected = IllegalArgumentException.class) - public void testNetworkSpecifierWithClientLongPassphrase() throws Exception { - executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, - "0123456789012345678901234567890123456789012345678901234567890123456789"); + public void testNetworkSpecifierWithClientTooLongPassphrase() throws Exception { + executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, PASSPHRASE_TOO_LONG); } /** @@ -1063,8 +1089,16 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierWithClientNullPeer() throws Exception { - executeNetworkSpecifierWithClient(null, false, null, - "0123456789012345678901234567890123456789012345678901234567890123456789"); + executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID); + } + + /** + * Validate that a null PeerHandle does not trigger an exception for legacy API. + */ + @Test + public void testNetworkSpecifierWithClientNullPeerLegacyApi() throws Exception { + mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.O; + executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID); } private void executeNetworkSpecifierWithClient(PeerHandle peerHandle, boolean doPmk, byte[] pmk, @@ -1117,7 +1151,7 @@ public class WifiAwareManagerTest { @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierDirectNullPmk() throws Exception { executeNetworkSpecifierDirect(HexEncoding.decode("000102030405".toCharArray(), false), true, - null, null); + null, null, true); } /** @@ -1126,7 +1160,7 @@ public class WifiAwareManagerTest { @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierDirectIncorrectLengthPmk() throws Exception { executeNetworkSpecifierDirect(HexEncoding.decode("000102030405".toCharArray(), false), true, - "012".getBytes(), null); + PMK_INVALID, null, true); } /** @@ -1135,40 +1169,57 @@ public class WifiAwareManagerTest { @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierDirectNullPassphrase() throws Exception { executeNetworkSpecifierDirect(HexEncoding.decode("000102030405".toCharArray(), false), - false, null, null); + false, null, null, true); } /** * Validate that a too short Passphrase triggers an exception. */ @Test(expected = IllegalArgumentException.class) - public void testNetworkSpecifierDirectShortPassphrase() throws Exception { + public void testNetworkSpecifierDirectTooShortPassphrase() throws Exception { executeNetworkSpecifierDirect(HexEncoding.decode("000102030405".toCharArray(), false), - false, null, "012"); + false, null, PASSPHRASE_TOO_SHORT, true); } /** * Validate that a too long Passphrase triggers an exception. */ @Test(expected = IllegalArgumentException.class) - public void testNetworkSpecifierDirectLongPassphrase() throws Exception { + public void testNetworkSpecifierDirectTooLongPassphrase() throws Exception { executeNetworkSpecifierDirect(HexEncoding.decode("000102030405".toCharArray(), false), - false, null, - "0123456789012345678901234567890123456789012345678901234567890123456789"); + false, null, PASSPHRASE_TOO_LONG, true); } /** - * Validate that a null peer MAC triggers an exception. + * Validate that a null peer MAC triggers an exception for an Initiator. */ @Test(expected = IllegalArgumentException.class) - public void testNetworkSpecifierDirectNullPeer() throws Exception { - executeNetworkSpecifierDirect(null, false, null, null); + public void testNetworkSpecifierDirectNullPeerInitiator() throws Exception { + executeNetworkSpecifierDirect(null, false, null, PASSPHRASE_VALID, true); + } + + /** + * Validate that a null peer MAC triggers an exception for a Resonder. + */ + @Test(expected = IllegalArgumentException.class) + public void testNetworkSpecifierDirectNullPeerResponder() throws Exception { + executeNetworkSpecifierDirect(null, false, null, PASSPHRASE_VALID, false); + } + + /** + * Validate that a null peer MAC does not trigger an exception for a Resonder on legacy API. + */ + @Test + public void testNetworkSpecifierDirectNullPeerResponderLegacyApi() throws Exception { + mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.O; + executeNetworkSpecifierDirect(null, false, null, PASSPHRASE_VALID, false); } private void executeNetworkSpecifierDirect(byte[] someMac, boolean doPmk, byte[] pmk, - String passphrase) throws Exception { + String passphrase, boolean doInitiator) throws Exception { final int clientId = 134; - final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR; + final int role = doInitiator ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR + : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER; final ConfigRequest configRequest = new ConfigRequest.Builder().build(); ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass( |