diff options
Diffstat (limited to 'packages/SystemUI/src')
342 files changed, 21117 insertions, 16941 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java index 13c48d0d7635..45d1aad8d3a4 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java @@ -268,6 +268,18 @@ public class CarrierText extends TextView { } } + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + + // Only show marquee when visible + if (visibility == VISIBLE) { + setEllipsize(TextUtils.TruncateAt.MARQUEE); + } else { + setEllipsize(TextUtils.TruncateAt.END); + } + } + /** * Top-level function for creating carrier text. Makes text based on simState, PLMN * and SPN as well as device capabilities, such as being emergency call capable. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java index abc3b94777b0..d63ad0840734 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java @@ -16,8 +16,8 @@ package com.android.keyguard; -import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL; -import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; +import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; +import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; import android.content.Context; import android.os.AsyncTask; @@ -29,6 +29,7 @@ import android.view.KeyEvent; import android.view.View; import android.widget.LinearLayout; +import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternChecker; import com.android.internal.widget.LockPatternUtils; @@ -223,8 +224,9 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout @Override public void onTick(long millisUntilFinished) { int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); - mSecurityMessageDisplay.formatMessage( - R.string.kg_too_many_failed_attempts_countdown, secondsRemaining); + mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString( + R.plurals.kg_too_many_failed_attempts_countdown, + secondsRemaining, secondsRemaining)); } @Override @@ -278,7 +280,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout @Override public void showPromptReason(int reason) { if (reason != PROMPT_REASON_NONE) { - int promtReasonStringRes = getPromtReasonStringRes(reason); + int promtReasonStringRes = getPromptReasonStringRes(reason); if (promtReasonStringRes != 0) { mSecurityMessageDisplay.setMessage(promtReasonStringRes); } @@ -286,12 +288,12 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout } @Override - public void showMessage(String message, int color) { + public void showMessage(CharSequence message, int color) { mSecurityMessageDisplay.setNextMessageColor(color); mSecurityMessageDisplay.setMessage(message); } - protected abstract int getPromtReasonStringRes(int reason); + protected abstract int getPromptReasonStringRes(int reason); // Cause a VIRTUAL_KEY vibration public void doHapticKeyClick() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java index cb5afec79073..b8a07cdbdc41 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java @@ -16,14 +16,18 @@ package com.android.keyguard; +import android.app.AlertDialog; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Handler; +import android.os.HandlerThread; import android.os.UserHandle; import android.util.AttributeSet; import android.view.View; +import android.view.WindowManager; import android.widget.Button; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionInfo; @@ -50,8 +54,17 @@ class KeyguardEsimArea extends Button implements View.OnClickListener { if (ACTION_DISABLE_ESIM.equals(intent.getAction())) { int resultCode = getResultCode(); if (resultCode != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) { - // TODO (b/62680294): Surface more info. to the end users for this failure. Log.e(TAG, "Error disabling esim, result code = " + resultCode); + AlertDialog.Builder builder = + new AlertDialog.Builder(mContext) + .setMessage(R.string.error_disable_esim_msg) + .setTitle(R.string.error_disable_esim_title) + .setCancelable(false /* cancelable */) + .setNeutralButton(R.string.ok, null /* listener */); + AlertDialog alertDialog = builder.create(); + alertDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + alertDialog.show(); } } } @@ -101,14 +114,13 @@ class KeyguardEsimArea extends Button implements View.OnClickListener { @Override public void onClick(View v) { - Intent intent = new Intent(mContext, KeyguardEsimArea.class); - intent.setAction(ACTION_DISABLE_ESIM); + Intent intent = new Intent(ACTION_DISABLE_ESIM); intent.setPackage(mContext.getPackageName()); - PendingIntent callbackIntent = PendingIntent.getBroadcast( + PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser( mContext, 0 /* requestCode */, intent, - PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent.FLAG_UPDATE_CURRENT, UserHandle.SYSTEM); mEuiccManager .switchToSubscription(SubscriptionManager.INVALID_SUBSCRIPTION_ID, callbackIntent); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java index 27a3f7d44890..f1a5ca9fba93 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java @@ -34,6 +34,7 @@ import android.widget.FrameLayout; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.settingslib.Utils; import java.io.File; @@ -171,10 +172,14 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback { mSecurityContainer.showPromptReason(reason); } - public void showMessage(String message, int color) { + public void showMessage(CharSequence message, int color) { mSecurityContainer.showMessage(message, color); } + public void showErrorMessage(CharSequence message) { + showMessage(message, Utils.getColorError(mContext)); + } + /** * Dismisses the keyguard by going to the next screen or making it gone. * @param targetUserId a user that needs to be the foreground user at the dismissal completion. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index b6184a883210..ff5f5e77b3f9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -117,7 +117,7 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView } @Override - protected int getPromtReasonStringRes(int reason) { + protected int getPromptReasonStringRes(int reason) { switch (reason) { case PROMPT_REASON_RESTART: return R.string.kg_prompt_reason_restart_password; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index 3c9a6b9dcdec..cb066a10a9c9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -15,8 +15,8 @@ */ package com.android.keyguard; -import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL; -import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; +import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; +import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; import android.content.Context; import android.graphics.Rect; @@ -33,6 +33,7 @@ import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.LinearLayout; +import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternChecker; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternView; @@ -332,8 +333,9 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit @Override public void onTick(long millisUntilFinished) { final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); - mSecurityMessageDisplay.formatMessage( - R.string.kg_too_many_failed_attempts_countdown, secondsRemaining); + mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString( + R.plurals.kg_too_many_failed_attempts_countdown, + secondsRemaining, secondsRemaining)); } @Override @@ -396,7 +398,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit } @Override - public void showMessage(String message, int color) { + public void showMessage(CharSequence message, int color) { mSecurityMessageDisplay.setNextMessageColor(color); mSecurityMessageDisplay.setMessage(message); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index c04ae68dab36..6539ccffc61b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -103,7 +103,7 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView } @Override - protected int getPromtReasonStringRes(int reason) { + protected int getPromptReasonStringRes(int reason) { switch (reason) { case PROMPT_REASON_RESTART: return R.string.kg_prompt_reason_restart_pin; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 27bc599f7f52..8dc4609f1b9d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -49,6 +49,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe private boolean mIsVerifyUnlockOnly; private SecurityMode mCurrentSecuritySelection = SecurityMode.Invalid; private SecurityCallback mSecurityCallback; + private AlertDialog mAlertDialog; private final KeyguardUpdateMonitor mUpdateMonitor; @@ -95,6 +96,10 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe @Override public void onPause() { + if (mAlertDialog != null) { + mAlertDialog.dismiss(); + mAlertDialog = null; + } if (mCurrentSecuritySelection != SecurityMode.None) { getSecurityView(mCurrentSecuritySelection).onPause(); } @@ -174,16 +179,20 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe } private void showDialog(String title, String message) { - final AlertDialog dialog = new AlertDialog.Builder(mContext) + if (mAlertDialog != null) { + mAlertDialog.dismiss(); + } + + mAlertDialog = new AlertDialog.Builder(mContext) .setTitle(title) .setMessage(message) .setCancelable(false) .setNeutralButton(R.string.ok, null) .create(); if (!(mContext instanceof Activity)) { - dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); } - dialog.show(); + mAlertDialog.show(); } private void showTimeoutDialog(int userId, int timeoutMs) { @@ -534,8 +543,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe } } - - public void showMessage(String message, int color) { + public void showMessage(CharSequence message, int color) { if (mCurrentSecuritySelection != SecurityMode.None) { getSecurityView(mCurrentSecuritySelection).showMessage(message, color); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java index 829084202f5a..360dba3bc0ea 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java @@ -106,7 +106,7 @@ public interface KeyguardSecurityView { * @param message the message to show * @param color the color to use */ - void showMessage(String message, int color); + void showMessage(CharSequence message, int color); /** * Instruct the view to show usability hints, if any. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java index 6012c4501412..a2ff8f7896aa 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java @@ -139,7 +139,7 @@ public class KeyguardSecurityViewFlipper extends ViewFlipper implements Keyguard } @Override - public void showMessage(String message, int color) { + public void showMessage(CharSequence message, int color) { KeyguardSecurityView ksv = getSecurityView(); if (ksv != null) { ksv.showMessage(message, color); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java index 6e0b56e25a3f..703b20531390 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java @@ -131,7 +131,7 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { } if (isEsimLocked) { - msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim); + msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); } mSecurityMessageDisplay.setMessage(msg); @@ -168,7 +168,7 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { } @Override - protected int getPromtReasonStringRes(int reason) { + protected int getPromptReasonStringRes(int reason) { // No message on SIM Pin return 0; } @@ -187,6 +187,10 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed; displayMessage = getContext().getString(msgId); } + if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) { + displayMessage = getResources() + .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); + } if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:" + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); return displayMessage; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java index 876d170e08cd..347c9792ec95 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java @@ -181,7 +181,7 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { } } if (isEsimLocked) { - msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim); + msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); } mSecurityMessageDisplay.setMessage(msg); mSimImageView.setImageTintList(ColorStateList.valueOf(color)); @@ -211,7 +211,7 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { } @Override - protected int getPromtReasonStringRes(int reason) { + protected int getPromptReasonStringRes(int reason) { // No message on SIM Puk return 0; } @@ -231,6 +231,10 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { R.string.kg_password_puk_failed; displayMessage = getContext().getString(msgId); } + if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) { + displayMessage = getResources() + .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); + } if (DEBUG) Log.d(LOG_TAG, "getPukPasswordErrorMessage:" + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); return displayMessage; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java new file mode 100644 index 000000000000..5d1bdabeeac8 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -0,0 +1,332 @@ +/* + * 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.keyguard; + +import android.app.PendingIntent; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.provider.Settings; +import android.text.Layout; +import android.text.TextUtils; +import android.text.TextUtils.TruncateAt; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.internal.graphics.ColorUtils; +import com.android.settingslib.Utils; +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.keyguard.KeyguardSliceProvider; +import com.android.systemui.tuner.TunerService; + +import java.util.HashMap; +import java.util.List; +import java.util.function.Consumer; + +import androidx.app.slice.Slice; +import androidx.app.slice.SliceItem; +import androidx.app.slice.core.SliceQuery; +import androidx.app.slice.widget.ListContent; +import androidx.app.slice.widget.RowContent; +import androidx.app.slice.widget.SliceLiveData; + +/** + * View visible under the clock on the lock screen and AoD. + */ +public class KeyguardSliceView extends LinearLayout implements View.OnClickListener, + Observer<Slice>, TunerService.Tunable { + + private static final String TAG = "KeyguardSliceView"; + private final HashMap<View, PendingIntent> mClickActions; + private Uri mKeyguardSliceUri; + private TextView mTitle; + private LinearLayout mRow; + private int mTextColor; + private float mDarkAmount = 0; + + private LiveData<Slice> mLiveData; + private int mIconSize; + private Consumer<Boolean> mListener; + private boolean mHasHeader; + + public KeyguardSliceView(Context context) { + this(context, null, 0); + } + + public KeyguardSliceView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TunerService tunerService = Dependency.get(TunerService.class); + tunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI); + + mClickActions = new HashMap<>(); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mTitle = findViewById(R.id.title); + mRow = findViewById(R.id.row); + mTextColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor); + mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + // Make sure we always have the most current slice + mLiveData.observeForever(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + mLiveData.removeObserver(this); + } + + private void showSlice(Slice slice) { + + ListContent lc = new ListContent(slice); + mHasHeader = lc.hasHeader(); + List<SliceItem> subItems = lc.getRowItems(); + if (!mHasHeader) { + mTitle.setVisibility(GONE); + } else { + mTitle.setVisibility(VISIBLE); + // If there's a header it'll be the first subitem + RowContent header = new RowContent(subItems.get(0), true /* showStartItem */); + SliceItem mainTitle = header.getTitleItem(); + CharSequence title = mainTitle != null ? mainTitle.getText() : null; + mTitle.setText(title); + + // Check if we're already ellipsizing the text. + // We're going to figure out the best possible line break if not. + Layout layout = mTitle.getLayout(); + if (layout != null){ + final int lineCount = layout.getLineCount(); + if (lineCount > 0) { + if (layout.getEllipsisCount(lineCount - 1) == 0) { + mTitle.setText(findBestLineBreak(title)); + } + } + } + } + + mClickActions.clear(); + final int subItemsCount = subItems.size(); + final int blendedColor = getTextColor(); + final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it + for (int i = startIndex; i < subItemsCount; i++) { + SliceItem item = subItems.get(i); + RowContent rc = new RowContent(item, true /* showStartItem */); + final Uri itemTag = item.getSlice().getUri(); + // Try to reuse the view if already exists in the layout + KeyguardSliceButton button = mRow.findViewWithTag(itemTag); + if (button == null) { + button = new KeyguardSliceButton(mContext); + button.setTextColor(blendedColor); + button.setTag(itemTag); + } else { + mRow.removeView(button); + } + mRow.addView(button); + + PendingIntent pendingIntent = null; + if (rc.getContentIntent() != null) { + pendingIntent = rc.getContentIntent().getAction(); + } + mClickActions.put(button, pendingIntent); + + button.setText(rc.getTitleItem().getText()); + + Drawable iconDrawable = null; + SliceItem icon = SliceQuery.find(item.getSlice(), + android.app.slice.SliceItem.FORMAT_IMAGE); + if (icon != null) { + iconDrawable = icon.getIcon().loadDrawable(mContext); + final int width = (int) (iconDrawable.getIntrinsicWidth() + / (float) iconDrawable.getIntrinsicHeight() * mIconSize); + iconDrawable.setBounds(0, 0, Math.max(width, 1), mIconSize); + } + button.setCompoundDrawablesRelative(iconDrawable, null, null, null); + button.setOnClickListener(this); + } + + // Removing old views + for (int i = 0; i < mRow.getChildCount(); i++) { + View child = mRow.getChildAt(i); + if (!mClickActions.containsKey(child)) { + mRow.removeView(child); + i--; + } + } + + final int visibility = mHasHeader || subItemsCount > 0 ? VISIBLE : GONE; + if (visibility != getVisibility()) { + setVisibility(visibility); + } + + mListener.accept(mHasHeader); + } + + /** + * Breaks a string in 2 lines where both have similar character count + * but first line is always longer. + * + * @param charSequence Original text. + * @return Optimal string. + */ + private CharSequence findBestLineBreak(CharSequence charSequence) { + if (TextUtils.isEmpty(charSequence)) { + return charSequence; + } + + String source = charSequence.toString(); + // Ignore if there is only 1 word, + // or if line breaks were manually set. + if (source.contains("\n") || !source.contains(" ")) { + return source; + } + + final String[] words = source.split(" "); + final StringBuilder optimalString = new StringBuilder(source.length()); + int current = 0; + while (optimalString.length() < source.length() - optimalString.length()) { + optimalString.append(words[current]); + if (current < words.length - 1) { + optimalString.append(" "); + } + current++; + } + optimalString.append("\n"); + for (int i = current; i < words.length; i++) { + optimalString.append(words[i]); + if (current < words.length - 1) { + optimalString.append(" "); + } + } + + return optimalString.toString(); + } + + public void setDark(float darkAmount) { + mDarkAmount = darkAmount; + updateTextColors(); + } + + private void updateTextColors() { + final int blendedColor = getTextColor(); + mTitle.setTextColor(blendedColor); + int childCount = mRow.getChildCount(); + for (int i = 0; i < childCount; i++) { + View v = mRow.getChildAt(i); + if (v instanceof Button) { + ((Button) v).setTextColor(blendedColor); + } + } + } + + @Override + public void onClick(View v) { + final PendingIntent action = mClickActions.get(v); + if (action != null) { + try { + action.send(); + } catch (PendingIntent.CanceledException e) { + Log.i(TAG, "Pending intent cancelled, nothing to launch", e); + } + } + } + + public void setListener(Consumer<Boolean> listener) { + mListener = listener; + } + + public boolean hasHeader() { + return mHasHeader; + } + + /** + * LiveData observer lifecycle. + * @param slice the new slice content. + */ + @Override + public void onChanged(Slice slice) { + showSlice(slice); + } + + @Override + public void onTuningChanged(String key, String newValue) { + setupUri(newValue); + } + + public void setupUri(String uriString) { + if (uriString == null) { + uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI; + } + + boolean wasObserving = false; + if (mLiveData != null && mLiveData.hasActiveObservers()) { + wasObserving = true; + mLiveData.removeObserver(this); + } + + mKeyguardSliceUri = Uri.parse(uriString); + mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri); + + if (wasObserving) { + mLiveData.observeForever(this); + } + } + + public int getTextColor() { + return ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount); + } + + /** + * Representation of an item that appears under the clock on main keyguard message. + */ + private class KeyguardSliceButton extends Button { + + public KeyguardSliceButton(Context context) { + super(context, null /* attrs */, 0 /* styleAttr */, + com.android.keyguard.R.style.TextAppearance_Keyguard_Secondary); + int horizontalPadding = (int) context.getResources() + .getDimension(R.dimen.widget_horizontal_padding); + setPadding(horizontalPadding / 2, 0, horizontalPadding / 2, 0); + setCompoundDrawablePadding((int) context.getResources() + .getDimension(R.dimen.widget_icon_padding)); + setMaxWidth(KeyguardSliceView.this.getWidth() / 2); + setMaxLines(1); + setEllipsize(TruncateAt.END); + } + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index bc2a59df42a6..e440731dcd47 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -16,20 +16,18 @@ package com.android.keyguard; -import android.app.ActivityManager; import android.app.AlarmManager; import android.content.Context; -import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; -import android.graphics.PorterDuff; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.support.v4.graphics.ColorUtils; import android.text.TextUtils; import android.text.format.DateFormat; +import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; import android.util.Slog; @@ -40,10 +38,9 @@ import android.widget.GridLayout; import android.widget.TextClock; import android.widget.TextView; -import com.android.internal.util.ArrayUtils; import com.android.internal.widget.LockPatternUtils; -import com.android.systemui.ChargingView; -import com.android.systemui.statusbar.policy.DateView; + +import com.google.android.collect.Sets; import java.util.Locale; @@ -54,23 +51,21 @@ public class KeyguardStatusView extends GridLayout { private final LockPatternUtils mLockPatternUtils; private final AlarmManager mAlarmManager; + private final float mSmallClockScale; + private final float mWidgetPadding; - private TextView mAlarmStatusView; - private DateView mDateView; private TextClock mClockView; + private View mClockSeparator; private TextView mOwnerInfo; private ViewGroup mClockContainer; - private ChargingView mBatteryDoze; - private View mKeyguardStatusArea; + private KeyguardSliceView mKeyguardSlice; private Runnable mPendingMarqueeStart; private Handler mHandler; - private View[] mVisibleInDoze; + private ArraySet<View> mVisibleInDoze; private boolean mPulsing; private float mDarkAmount = 0; private int mTextColor; - private int mDateTextColor; - private int mAlarmTextColor; private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { @@ -118,6 +113,9 @@ public class KeyguardStatusView extends GridLayout { mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); mLockPatternUtils = new LockPatternUtils(getContext()); mHandler = new Handler(Looper.myLooper()); + mSmallClockScale = getResources().getDimension(R.dimen.widget_small_font_size) + / getResources().getDimension(R.dimen.widget_big_font_size); + mWidgetPadding = getResources().getDimension(R.dimen.widget_vertical_padding); } private void setEnableMarquee(boolean enabled) { @@ -141,7 +139,6 @@ public class KeyguardStatusView extends GridLayout { private void setEnableMarqueeImpl(boolean enabled) { if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee"); - if (mAlarmStatusView != null) mAlarmStatusView.setSelected(enabled); if (mOwnerInfo != null) mOwnerInfo.setSelected(enabled); } @@ -149,20 +146,19 @@ public class KeyguardStatusView extends GridLayout { protected void onFinishInflate() { super.onFinishInflate(); mClockContainer = findViewById(R.id.keyguard_clock_container); - mAlarmStatusView = findViewById(R.id.alarm_status); - mDateView = findViewById(R.id.date_view); mClockView = findViewById(R.id.clock_view); mClockView.setShowCurrentUserTime(true); if (KeyguardClockAccessibilityDelegate.isNeeded(mContext)) { mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext)); } mOwnerInfo = findViewById(R.id.owner_info); - mBatteryDoze = findViewById(R.id.battery_doze); - mKeyguardStatusArea = findViewById(R.id.keyguard_status_area); - mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardStatusArea}; + mKeyguardSlice = findViewById(R.id.keyguard_status_area); + mClockSeparator = findViewById(R.id.clock_separator); + mVisibleInDoze = Sets.newArraySet(mClockView, mKeyguardSlice, mClockSeparator); mTextColor = mClockView.getCurrentTextColor(); - mDateTextColor = mDateView.getCurrentTextColor(); - mAlarmTextColor = mAlarmStatusView.getCurrentTextColor(); + + mKeyguardSlice.setListener(this::onSliceContentChanged); + onSliceContentChanged(mKeyguardSlice.hasHeader()); boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive(); setEnableMarquee(shouldMarquee); @@ -174,6 +170,19 @@ public class KeyguardStatusView extends GridLayout { mClockView.setElegantTextHeight(false); } + private void onSliceContentChanged(boolean hasHeader) { + final boolean smallClock = hasHeader || mPulsing; + final float clockScale = smallClock ? mSmallClockScale : 1; + float translation = (mClockView.getHeight() - (mClockView.getHeight() * clockScale)) / 2f; + if (smallClock) { + translation -= mWidgetPadding; + } + mClockView.setTranslationY(translation); + mClockView.setScaleX(clockScale); + mClockView.setScaleY(clockScale); + mClockSeparator.setVisibility(hasHeader && !mPulsing ? VISIBLE : GONE); + } + @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -184,8 +193,6 @@ public class KeyguardStatusView extends GridLayout { layoutParams.bottomMargin = getResources().getDimensionPixelSize( R.dimen.bottom_text_spacing_digital); mClockView.setLayoutParams(layoutParams); - mDateView.setTextSize(TypedValue.COMPLEX_UNIT_PX, - getResources().getDimensionPixelSize(R.dimen.widget_label_font_size)); if (mOwnerInfo != null) { mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimensionPixelSize(R.dimen.widget_label_font_size)); @@ -193,8 +200,6 @@ public class KeyguardStatusView extends GridLayout { } public void refreshTime() { - mDateView.setDatePattern(Patterns.dateViewSkel); - mClockView.setFormat12Hour(Patterns.clockView12); mClockView.setFormat24Hour(Patterns.clockView24); } @@ -205,40 +210,17 @@ public class KeyguardStatusView extends GridLayout { Patterns.update(mContext, nextAlarm != null); refreshTime(); - refreshAlarmStatus(nextAlarm); - } - - void refreshAlarmStatus(AlarmManager.AlarmClockInfo nextAlarm) { - if (nextAlarm != null) { - String alarm = formatNextAlarm(mContext, nextAlarm); - mAlarmStatusView.setText(alarm); - mAlarmStatusView.setContentDescription( - getResources().getString(R.string.keyguard_accessibility_next_alarm, alarm)); - mAlarmStatusView.setVisibility(View.VISIBLE); - } else { - mAlarmStatusView.setVisibility(View.GONE); - } } public int getClockBottom() { - return mKeyguardStatusArea.getBottom(); + return mKeyguardSlice.getVisibility() == VISIBLE ? mKeyguardSlice.getBottom() + : mClockView.getBottom(); } public float getClockTextSize() { return mClockView.getTextSize(); } - public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) { - if (info == null) { - return ""; - } - String skeleton = DateFormat.is24HourFormat(context, ActivityManager.getCurrentUser()) - ? "EHm" - : "Ehma"; - String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); - return DateFormat.format(pattern, info.getTriggerTime()).toString(); - } - private void updateOwnerInfo() { if (mOwnerInfo == null) return; String ownerInfo = getOwnerInfo(); @@ -320,7 +302,7 @@ public class KeyguardStatusView extends GridLayout { } } - public void setDark(float darkAmount) { + public void setDarkAmount(float darkAmount) { if (mDarkAmount == darkAmount) { return; } @@ -330,7 +312,7 @@ public class KeyguardStatusView extends GridLayout { final int N = mClockContainer.getChildCount(); for (int i = 0; i < N; i++) { View child = mClockContainer.getChildAt(i); - if (ArrayUtils.contains(mVisibleInDoze, child)) { + if (mVisibleInDoze.contains(child)) { continue; } child.setAlpha(dark ? 0 : 1); @@ -339,17 +321,17 @@ public class KeyguardStatusView extends GridLayout { mOwnerInfo.setAlpha(dark ? 0 : 1); } + final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount); updateDozeVisibleViews(); - mBatteryDoze.setDark(dark); - mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount)); - mDateView.setTextColor(ColorUtils.blendARGB(mDateTextColor, Color.WHITE, darkAmount)); - int blendedAlarmColor = ColorUtils.blendARGB(mAlarmTextColor, Color.WHITE, darkAmount); - mAlarmStatusView.setTextColor(blendedAlarmColor); - mAlarmStatusView.setCompoundDrawableTintList(ColorStateList.valueOf(blendedAlarmColor)); + mKeyguardSlice.setDark(darkAmount); + mClockView.setTextColor(blendedTextColor); + mClockSeparator.setBackgroundColor(blendedTextColor); } public void setPulsing(boolean pulsing) { mPulsing = pulsing; + mKeyguardSlice.setVisibility(pulsing ? INVISIBLE : VISIBLE); + onSliceContentChanged(mKeyguardSlice.hasHeader()); updateDozeVisibleViews(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 3a5d137d6d11..9e4b4055a289 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.ACTION_USER_UNLOCKED; import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN; import static android.os.BatteryManager.BATTERY_STATUS_FULL; @@ -50,7 +52,6 @@ import android.media.AudioManager; import android.os.BatteryManager; import android.os.CancellationSignal; import android.os.Handler; -import android.os.IBinder; import android.os.IRemoteCallback; import android.os.Message; import android.os.RemoteException; @@ -70,13 +71,15 @@ import android.util.Log; import android.util.SparseBooleanArray; import android.util.SparseIntArray; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.IccCardConstants.State; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.widget.LockPatternUtils; +import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.google.android.collect.Lists; @@ -345,6 +348,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { private SparseBooleanArray mUserFaceUnlockRunning = new SparseBooleanArray(); private static int sCurrentUser; + private Runnable mUpdateFingerprintListeningState = this::updateFingerprintListeningState; public synchronized static void setCurrentUser(int currentUser) { sCurrentUser = currentUser; @@ -368,6 +372,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } } + @Override + public void onTrustError(CharSequence message) { + dispatchErrorMessage(message); + } + protected void handleSimSubscriptionInfoChanged() { if (DEBUG_SIM_STATES) { Log.v(TAG, "onSubscriptionInfoChanged()"); @@ -452,6 +461,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { */ public void setKeyguardGoingAway(boolean goingAway) { mKeyguardGoingAway = goingAway; + updateFingerprintListeningState(); } /** @@ -701,6 +711,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { return mScreenOn; } + private void dispatchErrorMessage(CharSequence message) { + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onTrustAgentErrorMessage(message); + } + } + } + static class DisplayClientState { public int clientGeneration; public boolean clearing; @@ -972,6 +991,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { maxChargingWattage > fastThreshold ? CHARGING_FAST : CHARGING_REGULAR; } + + @Override + public String toString() { + return "BatteryStatus{status=" + status + ",level=" + level + ",plugged=" + plugged + + ",health=" + health + ",maxChargingWattage=" + maxChargingWattage + "}"; + } } public class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker { @@ -1107,7 +1132,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } } - private KeyguardUpdateMonitor(Context context) { + @VisibleForTesting + protected KeyguardUpdateMonitor(Context context) { mContext = context; mSubscriptionManager = SubscriptionManager.from(context); mDeviceProvisioned = isDeviceProvisionedInSettingsDb(); @@ -1185,7 +1211,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { mFpm.addLockoutResetCallback(mLockoutResetCallback); } - SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener); + ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); mUserManager = context.getSystemService(UserManager.class); } @@ -1231,7 +1257,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { mFingerprintCancelSignal.cancel(); } mFingerprintCancelSignal = new CancellationSignal(); - mFpm.authenticate(null, mFingerprintCancelSignal, 0, mAuthenticationCallback, null, userId); + mFpm.authenticate(null, mFingerprintCancelSignal, 0, mAuthenticationCallback, null, + userId); setFingerprintRunningState(FINGERPRINT_STATE_RUNNING); } } @@ -1593,11 +1620,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } } - private static boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) { + private boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) { final boolean nowPluggedIn = current.isPluggedIn(); final boolean wasPluggedIn = old.isPluggedIn(); - final boolean stateChangedWhilePluggedIn = - wasPluggedIn == true && nowPluggedIn == true + final boolean stateChangedWhilePluggedIn = wasPluggedIn && nowPluggedIn && (old.status != current.status); // change in plug state is always interesting @@ -1605,13 +1631,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { return true; } - // change in battery level while plugged in - if (nowPluggedIn && old.level != current.level) { - return true; - } - - // change where battery needs charging - if (!nowPluggedIn && current.isBatteryLow() && current.level != old.level) { + // change in battery level + if (old.level != current.level) { return true; } @@ -1663,7 +1684,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { public void setSwitchingUser(boolean switching) { mSwitchingUser = switching; - updateFingerprintListeningState(); + // Since this comes in on a binder thread, we need to post if first + mHandler.post(mUpdateFingerprintListeningState); } private void sendUpdates(KeyguardUpdateMonitorCallback callback) { @@ -1736,6 +1758,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { mFailedAttempts.delete(sCurrentUser); } + public ServiceState getServiceState(int subId) { + return mServiceStates.get(subId); + } + public int getFailedUnlockAttempts(int userId) { return mFailedAttempts.get(userId, 0); } @@ -1770,12 +1796,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } } - private final TaskStackListener mTaskStackListener = new TaskStackListener() { + private final SysUiTaskStackChangeListener + mTaskStackListener = new SysUiTaskStackChangeListener() { @Override public void onTaskStackChangedBackground() { try { ActivityManager.StackInfo info = ActivityManager.getService().getStackInfo( - ActivityManager.StackId.ASSISTANT_STACK_ID); + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT); if (info == null) { return; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 5a02178a5a7b..1afcca64de69 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -17,13 +17,14 @@ package com.android.keyguard; import android.app.admin.DevicePolicyManager; import android.graphics.Bitmap; +import android.hardware.fingerprint.FingerprintManager; import android.media.AudioManager; import android.os.SystemClock; -import android.hardware.fingerprint.FingerprintManager; import android.telephony.TelephonyManager; -import android.view.WindowManagerPolicy; +import android.view.WindowManagerPolicyConstants; import com.android.internal.telephony.IccCardConstants; +import com.android.systemui.statusbar.KeyguardIndicationController; /** * Callback for general information relevant to lock screen. @@ -171,9 +172,9 @@ public class KeyguardUpdateMonitorCallback { /** * Called when the device has finished going to sleep. - * @param why either {@link WindowManagerPolicy#OFF_BECAUSE_OF_ADMIN}, - * {@link WindowManagerPolicy#OFF_BECAUSE_OF_USER}, or - * {@link WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}. + * @param why either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_ADMIN}, + * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER}, or + * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}. * * @deprecated use {@link com.android.systemui.keyguard.WakefulnessLifecycle}. */ @@ -271,4 +272,15 @@ public class KeyguardUpdateMonitorCallback { * @param dreaming true if the dream's window has been created and is visible */ public void onDreamingStateChanged(boolean dreaming) { } + + /** + * Called when an error message needs to be presented on the keyguard. + * Message will be visible briefly, and might be overridden by other keyguard events, + * like fingerprint authentication errors. + * + * @param message Message that indicates an error. + * @see KeyguardIndicationController.BaseKeyguardCallback#HIDE_DELAY_MS + * @see KeyguardIndicationController#showTransientIndication(CharSequence) + */ + public void onTrustAgentErrorMessage(CharSequence message) { } } diff --git a/packages/SystemUI/src/com/android/keyguard/LatencyTracker.java b/packages/SystemUI/src/com/android/keyguard/LatencyTracker.java deleted file mode 100644 index cee0afcd37d6..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/LatencyTracker.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.keyguard; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Build; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.os.Trace; -import android.util.EventLog; -import android.util.Log; -import android.util.SparseLongArray; - -import com.android.systemui.EventLogTags; - -/** - * Class to track various latencies in SystemUI. It then outputs the latency to logcat so these - * latencies can be captured by tests and then used for dashboards. - * <p> - * This is currently only in Keyguard so it can be shared between SystemUI and Keyguard, but - * eventually we'd want to merge these two packages together so Keyguard can use common classes - * that are shared with SystemUI. - */ -public class LatencyTracker { - - private static final String ACTION_RELOAD_PROPERTY = - "com.android.systemui.RELOAD_LATENCY_TRACKER_PROPERTY"; - - private static final String TAG = "LatencyTracker"; - - /** - * Time it takes until the first frame of the notification panel to be displayed while expanding - */ - public static final int ACTION_EXPAND_PANEL = 0; - - /** - * Time it takes until the first frame of recents is drawn after invoking it with the button. - */ - public static final int ACTION_TOGGLE_RECENTS = 1; - - /** - * Time between we get a fingerprint acquired signal until we start with the unlock animation - */ - public static final int ACTION_FINGERPRINT_WAKE_AND_UNLOCK = 2; - - /** - * Time it takes to check PIN/Pattern/Password. - */ - public static final int ACTION_CHECK_CREDENTIAL = 3; - - /** - * Time it takes to check fully PIN/Pattern/Password, i.e. that's the time spent including the - * actions to unlock a user. - */ - public static final int ACTION_CHECK_CREDENTIAL_UNLOCKED = 4; - - /** - * Time it takes to turn on the screen. - */ - public static final int ACTION_TURN_ON_SCREEN = 5; - - private static final String[] NAMES = new String[] { - "expand panel", - "toggle recents", - "fingerprint wake-and-unlock", - "check credential", - "check credential unlocked", - "turn on screen" }; - - private static LatencyTracker sLatencyTracker; - - private final SparseLongArray mStartRtc = new SparseLongArray(); - private boolean mEnabled; - - public static LatencyTracker getInstance(Context context) { - if (sLatencyTracker == null) { - sLatencyTracker = new LatencyTracker(context); - } - return sLatencyTracker; - } - - private LatencyTracker(Context context) { - context.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - reloadProperty(); - } - }, new IntentFilter(ACTION_RELOAD_PROPERTY)); - reloadProperty(); - } - - private void reloadProperty() { - mEnabled = SystemProperties.getBoolean("debug.systemui.latency_tracking", false); - } - - public static boolean isEnabled(Context ctx) { - return Build.IS_DEBUGGABLE && getInstance(ctx).mEnabled; - } - - /** - * Notifies that an action is starting. This needs to be called from the main thread. - * - * @param action The action to start. One of the ACTION_* values. - */ - public void onActionStart(int action) { - if (!mEnabled) { - return; - } - Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, NAMES[action], 0); - mStartRtc.put(action, SystemClock.elapsedRealtime()); - } - - /** - * Notifies that an action has ended. This needs to be called from the main thread. - * - * @param action The action to end. One of the ACTION_* values. - */ - public void onActionEnd(int action) { - if (!mEnabled) { - return; - } - long endRtc = SystemClock.elapsedRealtime(); - long startRtc = mStartRtc.get(action, -1); - if (startRtc == -1) { - return; - } - mStartRtc.delete(action); - Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, NAMES[action], 0); - long duration = endRtc - startRtc; - Log.i(TAG, "action=" + action + " latency=" + duration); - EventLog.writeEvent(EventLogTags.SYSUI_LATENCY, action, (int) duration); - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java index 12f75bb2d56c..d3dded0e25b2 100644 --- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java +++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java @@ -31,6 +31,7 @@ import android.os.PowerManager; import android.os.SystemClock; import android.provider.Settings; import android.text.InputType; +import android.text.TextUtils; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; @@ -78,6 +79,8 @@ public class PasswordTextView extends View { */ private static final float OVERSHOOT_TIME_POSITION = 0.5f; + private static char DOT = '\u2022'; + /** * The raw text size, will be multiplied by the scaled density when drawn */ @@ -208,7 +211,7 @@ public class PasswordTextView extends View { public void append(char c) { int visibleChars = mTextChars.size(); - String textbefore = mText; + CharSequence textbefore = getTransformedText(); mText = mText + c; int newLength = mText.length(); CharState charState; @@ -245,7 +248,7 @@ public class PasswordTextView extends View { public void deleteLastChar() { int length = mText.length(); - String textbefore = mText; + CharSequence textbefore = getTransformedText(); if (length > 0) { mText = mText.substring(0, length - 1); CharState charState = mTextChars.get(length - 1); @@ -259,6 +262,21 @@ public class PasswordTextView extends View { return mText; } + private CharSequence getTransformedText() { + int textLength = mTextChars.size(); + StringBuilder stringBuilder = new StringBuilder(textLength); + for (int i = 0; i < textLength; i++) { + CharState charState = mTextChars.get(i); + // If the dot is disappearing, the character is disappearing entirely. Consider + // it gone. + if (charState.dotAnimator != null && !charState.dotAnimationIsGrowing) { + continue; + } + stringBuilder.append(charState.isCharVisibleForA11y() ? charState.whichChar : DOT); + } + return stringBuilder; + } + private CharState obtainCharState(char c) { CharState charState; if(mCharPool.isEmpty()) { @@ -272,7 +290,7 @@ public class PasswordTextView extends View { } public void reset(boolean animated, boolean announce) { - String textbefore = mText; + CharSequence textbefore = getTransformedText(); mText = ""; int length = mTextChars.size(); int middleIndex = (length - 1) / 2; @@ -305,7 +323,7 @@ public class PasswordTextView extends View { } } - void sendAccessibilityEventTypeViewTextChanged(String beforeText, int fromIndex, + void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount) { if (AccessibilityManager.getInstance(mContext).isEnabled() && (isFocused() || isSelected() && isShown())) { @@ -315,6 +333,10 @@ public class PasswordTextView extends View { event.setRemovedCount(removedCount); event.setAddedCount(addedCount); event.setBeforeText(beforeText); + CharSequence transformedText = getTransformedText(); + if (!TextUtils.isEmpty(transformedText)) { + event.getText().add(transformedText); + } event.setPassword(true); sendAccessibilityEventUnchecked(event); } @@ -332,8 +354,9 @@ public class PasswordTextView extends View { public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); - info.setClassName(PasswordTextView.class.getName()); + info.setClassName(EditText.class.getName()); info.setPassword(true); + info.setText(getTransformedText()); info.setEditable(true); @@ -420,7 +443,19 @@ public class PasswordTextView extends View { = new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { + boolean textVisibleBefore = isCharVisibleForA11y(); + float beforeTextSizeFactor = currentTextSizeFactor; currentTextSizeFactor = (float) animation.getAnimatedValue(); + if (textVisibleBefore != isCharVisibleForA11y()) { + currentTextSizeFactor = beforeTextSizeFactor; + CharSequence beforeText = getTransformedText(); + currentTextSizeFactor = (float) animation.getAnimatedValue(); + int indexOfThisChar = mTextChars.indexOf(CharState.this); + if (indexOfThisChar >= 0) { + sendAccessibilityEventTypeViewTextChanged( + beforeText, indexOfThisChar, 1, 1); + } + } invalidate(); } }; @@ -673,5 +708,13 @@ public class PasswordTextView extends View { } return charWidth + mCharPadding * currentWidthFactor; } + + public boolean isCharVisibleForA11y() { + // The text has size 0 when it is first added, but we want to count it as visible if + // it will become visible presently. Count text as visible if an animator + // is configured to make it grow. + boolean textIsGrowing = textAnimator != null && textAnimationIsGrowing; + return (currentTextSizeFactor > 0) || textIsGrowing; + } } } diff --git a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java index b194de43a718..5c6812367a15 100644 --- a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java @@ -76,6 +76,12 @@ public interface ViewMediatorCallback { void playTrustedSound(); /** + * When the bouncer is shown or hides + * @param shown + */ + void onBouncerVisiblityChanged(boolean shown); + + /** * @return true if the screen is on */ boolean isScreenOn(); @@ -93,4 +99,10 @@ public interface ViewMediatorCallback { * Invoked when the secondary display showing a keyguard window changes. */ void onSecondaryDisplayShowingChanged(int displayId); + + /** + * Consumes a message that was enqueued to be displayed on the next time the bouncer shows up. + * @return Message that should be displayed above the challenge. + */ + CharSequence consumeCustomMessage(); } diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index 2b31967cea4f..1ae06d751255 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -15,6 +15,8 @@ */ package com.android.systemui; +import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS; +import static android.app.StatusBarManager.DISABLE_NONE; import static android.provider.Settings.System.SHOW_BATTERY_PERCENT; import android.animation.ArgbEvaluator; @@ -52,6 +54,7 @@ import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver; import com.android.systemui.statusbar.policy.IconLogger; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; +import com.android.systemui.util.Utils.DisableStateTracker; import java.text.NumberFormat; @@ -101,6 +104,9 @@ public class BatteryMeterView extends LinearLayout implements mSettingObserver = new SettingObserver(new Handler(context.getMainLooper())); + addOnAttachStateChangeListener( + new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS)); + mSlotBattery = context.getString( com.android.internal.R.string.status_bar_battery); mBatteryIconView = new ImageView(context); @@ -113,18 +119,10 @@ public class BatteryMeterView extends LinearLayout implements addView(mBatteryIconView, mlp); updateShowPercent(); - - Context dualToneDarkTheme = new ContextThemeWrapper(context, - Utils.getThemeAttr(context, R.attr.darkIconTheme)); - Context dualToneLightTheme = new ContextThemeWrapper(context, - Utils.getThemeAttr(context, R.attr.lightIconTheme)); - mDarkModeBackgroundColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.backgroundColor); - mDarkModeFillColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.fillColor); - mLightModeBackgroundColor = Utils.getColorAttr(dualToneLightTheme, R.attr.backgroundColor); - mLightModeFillColor = Utils.getColorAttr(dualToneLightTheme, R.attr.fillColor); - + setColorsFromContext(context); // Init to not dark at all. onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT); + mUserTracker = new CurrentUserTracker(mContext) { @Override public void onUserSwitched(int newUserId) { @@ -142,6 +140,21 @@ public class BatteryMeterView extends LinearLayout implements updateShowPercent(); } + public void setColorsFromContext(Context context) { + if (context == null) { + return; + } + + Context dualToneDarkTheme = new ContextThemeWrapper(context, + Utils.getThemeAttr(context, R.attr.darkIconTheme)); + Context dualToneLightTheme = new ContextThemeWrapper(context, + Utils.getThemeAttr(context, R.attr.lightIconTheme)); + mDarkModeBackgroundColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.backgroundColor); + mDarkModeFillColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.fillColor); + mLightModeBackgroundColor = Utils.getColorAttr(dualToneLightTheme, R.attr.backgroundColor); + mLightModeFillColor = Utils.getColorAttr(dualToneLightTheme, R.attr.fillColor); + } + @Override public boolean hasOverlappingRendering() { return false; @@ -218,7 +231,6 @@ public class BatteryMeterView extends LinearLayout implements if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor); updatePercentText(); addView(mBatteryPercentView, - 0, new ViewGroup.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)); diff --git a/packages/SystemUI/src/com/android/systemui/ChargingView.java b/packages/SystemUI/src/com/android/systemui/ChargingView.java deleted file mode 100644 index 33f8b069b751..000000000000 --- a/packages/SystemUI/src/com/android/systemui/ChargingView.java +++ /dev/null @@ -1,126 +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 com.android.systemui; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.res.TypedArray; -import android.os.UserHandle; -import android.util.AttributeSet; -import android.widget.ImageView; - -import com.android.internal.hardware.AmbientDisplayConfiguration; -import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.statusbar.policy.ConfigurationController; - -/** - * A view that only shows its drawable while the phone is charging. - * - * Also reloads its drawable upon density changes. - */ -public class ChargingView extends ImageView implements - BatteryController.BatteryStateChangeCallback, - ConfigurationController.ConfigurationListener { - - private static final long CHARGING_INDICATION_DELAY_MS = 1000; - - private final AmbientDisplayConfiguration mConfig; - private final Runnable mClearSuppressCharging = this::clearSuppressCharging; - private BatteryController mBatteryController; - private int mImageResource; - private boolean mCharging; - private boolean mDark; - private boolean mSuppressCharging; - - - private void clearSuppressCharging() { - mSuppressCharging = false; - removeCallbacks(mClearSuppressCharging); - updateVisibility(); - } - - public ChargingView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - - mConfig = new AmbientDisplayConfiguration(context); - - TypedArray a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.src}); - int srcResId = a.getResourceId(0, 0); - - if (srcResId != 0) { - mImageResource = srcResId; - } - - a.recycle(); - - updateVisibility(); - } - - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - mBatteryController = Dependency.get(BatteryController.class); - mBatteryController.addCallback(this); - Dependency.get(ConfigurationController.class).addCallback(this); - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mBatteryController.removeCallback(this); - Dependency.get(ConfigurationController.class).removeCallback(this); - removeCallbacks(mClearSuppressCharging); - } - - @Override - public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { - boolean startCharging = charging && !mCharging; - if (startCharging && deviceWillWakeUpWhenPluggedIn() && mDark) { - // We're about to wake up, and thus don't want to show the indicator just for it to be - // hidden again. - clearSuppressCharging(); - mSuppressCharging = true; - postDelayed(mClearSuppressCharging, CHARGING_INDICATION_DELAY_MS); - } - mCharging = charging; - updateVisibility(); - } - - private boolean deviceWillWakeUpWhenPluggedIn() { - boolean plugTurnsOnScreen = getResources().getBoolean( - com.android.internal.R.bool.config_unplugTurnsOnScreen); - boolean aod = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT); - return !aod && plugTurnsOnScreen; - } - - @Override - public void onDensityOrFontScaleChanged() { - setImageResource(mImageResource); - } - - public void setDark(boolean dark) { - mDark = dark; - if (!dark) { - clearSuppressCharging(); - } - updateVisibility(); - } - - private void updateVisibility() { - setVisibility(mCharging && !mSuppressCharging && mDark ? VISIBLE : INVISIBLE); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/DemoMode.java b/packages/SystemUI/src/com/android/systemui/DemoMode.java index 11996d078bc3..5c3971571b87 100644 --- a/packages/SystemUI/src/com/android/systemui/DemoMode.java +++ b/packages/SystemUI/src/com/android/systemui/DemoMode.java @@ -37,4 +37,5 @@ public interface DemoMode { public static final String COMMAND_STATUS = "status"; public static final String COMMAND_NOTIFICATIONS = "notifications"; public static final String COMMAND_VOLUME = "volume"; + public static final String COMMAND_OPERATOR = "operator"; } diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index b25719b0272d..7403ddc441f6 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -26,7 +26,7 @@ import android.view.IWindowManager; import android.view.WindowManagerGlobal; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.app.NightDisplayController; +import com.android.internal.app.ColorDisplayController; import com.android.internal.logging.MetricsLogger; import com.android.internal.util.Preconditions; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -40,6 +40,8 @@ import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.PluginManagerImpl; import com.android.systemui.plugins.VolumeDialogController; +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.phone.ConfigurationControllerImpl; @@ -205,8 +207,8 @@ public class Dependency extends SystemUI { mProviders.put(BatteryController.class, () -> new BatteryControllerImpl(mContext)); - mProviders.put(NightDisplayController.class, () -> - new NightDisplayController(mContext)); + mProviders.put(ColorDisplayController.class, () -> + new ColorDisplayController(mContext)); mProviders.put(ManagedProfileController.class, () -> new ManagedProfileControllerImpl(mContext)); @@ -308,6 +310,10 @@ public class Dependency extends SystemUI { mProviders.put(IWindowManager.class, () -> WindowManagerGlobal.getWindowManagerService()); + mProviders.put(OverviewProxyService.class, () -> new OverviewProxyService(mContext)); + + mProviders.put(EnhancedEstimates.class, () -> new EnhancedEstimatesImpl()); + // Put all dependencies above here so the factory can override them if it wants. SystemUIFactory.getInstance().injectDependencies(mProviders, mContext); } @@ -352,7 +358,8 @@ public class Dependency extends SystemUI { @SuppressWarnings("unchecked") DependencyProvider<T> provider = mProviders.get(cls); if (provider == null) { - throw new IllegalArgumentException("Unsupported dependency " + cls); + throw new IllegalArgumentException("Unsupported dependency " + cls + + ". " + mProviders.size() + " providers known."); } return provider.createDependency(); } diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags index d4149ea6d68f..9c847be75fab 100644 --- a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags +++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags @@ -61,8 +61,3 @@ option java_package com.android.systemui; ## 4: SYSTEM_REGISTER_USER System sysui registers user's callbacks ## 5: SYSTEM_UNREGISTER_USER System sysui unregisters user's callbacks (after death) 36060 sysui_recents_connection (type|1),(user|1) - -# --------------------------- -# LatencyTracker.java -# --------------------------- -36070 sysui_latency (action|1|5),(latency|1|3) diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java index ca34345d923b..948178802003 100644 --- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java +++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java @@ -58,6 +58,7 @@ public class HardwareUiLayout extends FrameLayout implements Tunable { private boolean mRoundedDivider; private int mRotation = ROTATION_NONE; private boolean mRotatedBackground; + private boolean mSwapOrientation = true; public HardwareUiLayout(Context context, AttributeSet attrs) { super(context, attrs); @@ -145,6 +146,10 @@ public class HardwareUiLayout extends FrameLayout implements Tunable { updateRotation(); } + public void setSwapOrientation(boolean swapOrientation) { + mSwapOrientation = swapOrientation; + } + private void updateRotation() { int rotation = RotationUtils.getRotation(getContext()); if (rotation != mRotation) { @@ -173,7 +178,9 @@ public class HardwareUiLayout extends FrameLayout implements Tunable { if (to == ROTATION_SEASCAPE) { swapOrder(linearLayout); } - linearLayout.setOrientation(LinearLayout.HORIZONTAL); + if (mSwapOrientation) { + linearLayout.setOrientation(LinearLayout.HORIZONTAL); + } swapDimens(this.mChild); } } else { @@ -184,7 +191,9 @@ public class HardwareUiLayout extends FrameLayout implements Tunable { if (from == ROTATION_SEASCAPE) { swapOrder(linearLayout); } - linearLayout.setOrientation(LinearLayout.VERTICAL); + if (mSwapOrientation) { + linearLayout.setOrientation(LinearLayout.VERTICAL); + } swapDimens(mChild); } } diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 907a79e723ac..a59c97e0d08d 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -16,11 +16,6 @@ package com.android.systemui; -import static android.opengl.GLES20.*; - -import static javax.microedition.khronos.egl.EGL10.*; - -import android.app.ActivityManager; import android.app.WallpaperManager; import android.content.ComponentCallbacks2; import android.graphics.Bitmap; @@ -28,31 +23,18 @@ import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region.Op; -import android.opengl.GLUtils; import android.os.AsyncTask; -import android.os.SystemProperties; import android.os.Trace; -import android.renderscript.Matrix4f; import android.service.wallpaper.WallpaperService; import android.util.Log; import android.view.Display; import android.view.DisplayInfo; -import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.WindowManager; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.FloatBuffer; - -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; /** * Default built-in wallpaper that simply shows a static image. @@ -64,24 +46,13 @@ public class ImageWallpaper extends WallpaperService { private static final boolean DEBUG = false; private static final String PROPERTY_KERNEL_QEMU = "ro.kernel.qemu"; - static final boolean FIXED_SIZED_SURFACE = true; - static final boolean USE_OPENGL = true; - - WallpaperManager mWallpaperManager; - - DrawableEngine mEngine; - - boolean mIsHwAccelerated; + private WallpaperManager mWallpaperManager; + private DrawableEngine mEngine; @Override public void onCreate() { super.onCreate(); - mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE); - - //noinspection PointlessBooleanExpression,ConstantConditions - if (FIXED_SIZED_SURFACE && USE_OPENGL) { - mIsHwAccelerated = ActivityManager.isHighEndGfx(); - } + mWallpaperManager = getSystemService(WallpaperManager.class); } @Override @@ -98,15 +69,12 @@ public class ImageWallpaper extends WallpaperService { } class DrawableEngine extends Engine { - static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - static final int EGL_OPENGL_ES2_BIT = 4; - Bitmap mBackground; int mBackgroundWidth = -1, mBackgroundHeight = -1; int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1; int mLastRotation = -1; - float mXOffset = 0.5f; - float mYOffset = 0.5f; + float mXOffset = 0f; + float mYOffset = 0f; float mScale = 1f; private Display mDefaultDisplay; @@ -117,34 +85,6 @@ public class ImageWallpaper extends WallpaperService { int mLastXTranslation; int mLastYTranslation; - private EGL10 mEgl; - private EGLDisplay mEglDisplay; - private EGLConfig mEglConfig; - private EGLContext mEglContext; - private EGLSurface mEglSurface; - - private static final String sSimpleVS = - "attribute vec4 position;\n" + - "attribute vec2 texCoords;\n" + - "varying vec2 outTexCoords;\n" + - "uniform mat4 projection;\n" + - "\nvoid main(void) {\n" + - " outTexCoords = texCoords;\n" + - " gl_Position = projection * position;\n" + - "}\n\n"; - private static final String sSimpleFS = - "precision mediump float;\n\n" + - "varying vec2 outTexCoords;\n" + - "uniform sampler2D texture;\n" + - "\nvoid main(void) {\n" + - " gl_FragColor = texture2D(texture, outTexCoords);\n" + - "}\n\n"; - - private static final int FLOAT_SIZE_BYTES = 4; - private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; - private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; - private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; - private int mRotationAtLastSurfaceSizeUpdate = -1; private int mDisplayWidthAtLastSurfaceSizeUpdate = -1; private int mDisplayHeightAtLastSurfaceSizeUpdate = -1; @@ -154,13 +94,14 @@ public class ImageWallpaper extends WallpaperService { private AsyncTask<Void, Void, Bitmap> mLoader; private boolean mNeedsDrawAfterLoadingWallpaper; private boolean mSurfaceValid; + private boolean mSurfaceRedrawNeeded; - public DrawableEngine() { + DrawableEngine() { super(); setFixedSizeAllowed(true); } - public void trimMemory(int level) { + void trimMemory(int level) { if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW && level <= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL && mBackground != null) { @@ -179,6 +120,7 @@ public class ImageWallpaper extends WallpaperService { super.onCreate(surfaceHolder); + //noinspection ConstantConditions mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay(); setOffsetNotificationsEnabled(false); @@ -199,7 +141,7 @@ public class ImageWallpaper extends WallpaperService { // Load background image dimensions, if we haven't saved them yet if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) { // Need to load the image to get dimensions - loadWallpaper(forDraw, false /* needsReset */); + loadWallpaper(forDraw); if (DEBUG) { Log.d(TAG, "Reloading, redoing updateSurfaceSize later."); } @@ -210,16 +152,13 @@ public class ImageWallpaper extends WallpaperService { int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth); int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight); - if (FIXED_SIZED_SURFACE) { - // Used a fixed size surface, because we are special. We can do - // this because we know the current design of window animations doesn't - // cause this to break. - surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight); - mLastRequestedWidth = surfaceWidth; - mLastRequestedHeight = surfaceHeight; - } else { - surfaceHolder.setSizeFromLayout(); - } + // Used a fixed size surface, because we are special. We can do + // this because we know the current design of window animations doesn't + // cause this to break. + surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight); + mLastRequestedWidth = surfaceWidth; + mLastRequestedHeight = surfaceHeight; + return hasWallpaper; } @@ -300,6 +239,13 @@ public class ImageWallpaper extends WallpaperService { Log.d(TAG, "onSurfaceRedrawNeeded"); } super.onSurfaceRedrawNeeded(holder); + // At the end of this method we should have drawn into the surface. + // This means that the bitmap should be loaded synchronously if + // it was already unloaded. + if (mBackground == null) { + updateBitmap(mWallpaperManager.getBitmap(true /* hardware */)); + } + mSurfaceRedrawNeeded = true; drawFrame(); } @@ -336,7 +282,8 @@ public class ImageWallpaper extends WallpaperService { boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth || dh != mLastSurfaceHeight; - boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation; + boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation + || mSurfaceRedrawNeeded; if (!redrawNeeded && !mOffsetsChanged) { if (DEBUG) { Log.d(TAG, "Suppressed drawFrame since redraw is not needed " @@ -345,40 +292,24 @@ public class ImageWallpaper extends WallpaperService { return; } mLastRotation = newRotation; + mSurfaceRedrawNeeded = false; // Load bitmap if it is not yet loaded if (mBackground == null) { - if (DEBUG) { - Log.d(TAG, "Reloading bitmap: mBackground, bgw, bgh, dw, dh = " + - mBackground + ", " + - ((mBackground == null) ? 0 : mBackground.getWidth()) + ", " + - ((mBackground == null) ? 0 : mBackground.getHeight()) + ", " + - dw + ", " + dh); - } - loadWallpaper(true /* needDraw */, true /* needReset */); + loadWallpaper(true); if (DEBUG) { Log.d(TAG, "Reloading, resuming draw later"); } return; } - // Center the scaled image + // Left align the scaled image mScale = Math.max(1f, Math.max(dw / (float) mBackground.getWidth(), dh / (float) mBackground.getHeight())); - final int availw = dw - (int) (mBackground.getWidth() * mScale); - final int availh = dh - (int) (mBackground.getHeight() * mScale); - int xPixels = availw / 2; - int yPixels = availh / 2; - - // Adjust the image for xOffset/yOffset values. If window manager is handling offsets, - // mXOffset and mYOffset are set to 0.5f by default and therefore xPixels and yPixels - // will remain unchanged - final int availwUnscaled = dw - mBackground.getWidth(); - final int availhUnscaled = dh - mBackground.getHeight(); - if (availwUnscaled < 0) - xPixels += (int) (availwUnscaled * (mXOffset - .5f) + .5f); - if (availhUnscaled < 0) - yPixels += (int) (availhUnscaled * (mYOffset - .5f) + .5f); + final int availw = (int) (mBackground.getWidth() * mScale) - dw; + final int availh = (int) (mBackground.getHeight() * mScale) - dh; + int xPixels = (int) (availw * mXOffset); + int yPixels = (int) (availh * mYOffset); mOffsetsChanged = false; if (surfaceDimensionsChanged) { @@ -399,21 +330,7 @@ public class ImageWallpaper extends WallpaperService { Log.d(TAG, "Redrawing wallpaper"); } - if (mIsHwAccelerated) { - if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) { - drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels); - } - } else { - drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels); - if (FIXED_SIZED_SURFACE) { - // If the surface is fixed-size, we should only need to - // draw it once and then we'll let the window manager - // position it appropriately. As such, we no longer needed - // the loaded bitmap. Yay! - // hw-accelerated renderer retains bitmap for faster rotation - unloadWallpaper(false /* forgetSize */); - } - } + drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } @@ -428,28 +345,20 @@ public class ImageWallpaper extends WallpaperService { * * If {@param needsReset} is set also clears the cache in WallpaperManager first. */ - private void loadWallpaper(boolean needsDraw, boolean needsReset) { + private void loadWallpaper(boolean needsDraw) { mNeedsDrawAfterLoadingWallpaper |= needsDraw; if (mLoader != null) { - if (needsReset) { - mLoader.cancel(false /* interrupt */); - mLoader = null; - } else { - if (DEBUG) { - Log.d(TAG, "Skipping loadWallpaper, already in flight "); - } - return; + if (DEBUG) { + Log.d(TAG, "Skipping loadWallpaper, already in flight "); } + return; } mLoader = new AsyncTask<Void, Void, Bitmap>() { @Override protected Bitmap doInBackground(Void... params) { Throwable exception; try { - if (needsReset) { - mWallpaperManager.forgetLoadedWallpaper(); - } - return mWallpaperManager.getBitmap(); + return mWallpaperManager.getBitmap(true /* hardware */); } catch (RuntimeException | OutOfMemoryError e) { exception = e; } @@ -458,48 +367,33 @@ public class ImageWallpaper extends WallpaperService { return null; } - if (exception != null) { - // Note that if we do fail at this, and the default wallpaper can't - // be loaded, we will go into a cycle. Don't do a build where the - // default wallpaper can't be loaded. - Log.w(TAG, "Unable to load wallpaper!", exception); - try { - mWallpaperManager.clear(); - } catch (IOException ex) { - // now we're really screwed. - Log.w(TAG, "Unable reset to default wallpaper!", ex); - } - - if (isCancelled()) { - return null; - } - - try { - return mWallpaperManager.getBitmap(); - } catch (RuntimeException | OutOfMemoryError e) { - Log.w(TAG, "Unable to load default wallpaper!", e); - } + // Note that if we do fail at this, and the default wallpaper can't + // be loaded, we will go into a cycle. Don't do a build where the + // default wallpaper can't be loaded. + Log.w(TAG, "Unable to load wallpaper!", exception); + try { + mWallpaperManager.clear(); + } catch (IOException ex) { + // now we're really screwed. + Log.w(TAG, "Unable reset to default wallpaper!", ex); + } + + if (isCancelled()) { + return null; + } + + try { + return mWallpaperManager.getBitmap(true /* hardware */); + } catch (RuntimeException | OutOfMemoryError e) { + Log.w(TAG, "Unable to load default wallpaper!", e); } return null; } @Override protected void onPostExecute(Bitmap b) { - mBackground = null; - mBackgroundWidth = -1; - mBackgroundHeight = -1; - - if (b != null) { - mBackground = b; - mBackgroundWidth = mBackground.getWidth(); - mBackgroundHeight = mBackground.getHeight(); - } + updateBitmap(b); - if (DEBUG) { - Log.d(TAG, "Wallpaper loaded: " + mBackground); - } - updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(), - false /* forDraw */); if (mNeedsDrawAfterLoadingWallpaper) { drawFrame(); } @@ -510,6 +404,24 @@ public class ImageWallpaper extends WallpaperService { }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + private void updateBitmap(Bitmap bitmap) { + mBackground = null; + mBackgroundWidth = -1; + mBackgroundHeight = -1; + + if (bitmap != null) { + mBackground = bitmap; + mBackgroundWidth = mBackground.getWidth(); + mBackgroundHeight = mBackground.getHeight(); + } + + if (DEBUG) { + Log.d(TAG, "Wallpaper loaded: " + mBackground); + } + updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(), + false /* forDraw */); + } + private void unloadWallpaper(boolean forgetSize) { if (mLoader != null) { mLoader.cancel(false); @@ -564,7 +476,7 @@ public class ImageWallpaper extends WallpaperService { } private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int left, int top) { - Canvas c = sh.lockCanvas(); + Canvas c = sh.lockHardwareCanvas(); if (c != null) { try { if (DEBUG) { @@ -582,7 +494,8 @@ public class ImageWallpaper extends WallpaperService { } if (mBackground != null) { RectF dest = new RectF(left, top, right, bottom); - // add a filter bitmap? + Log.i(TAG, "Redrawing in rect: " + dest + " with surface size: " + + mLastRequestedWidth + "x" + mLastRequestedHeight); c.drawBitmap(mBackground, null, dest, null); } } finally { @@ -590,278 +503,5 @@ public class ImageWallpaper extends WallpaperService { } } } - - private boolean drawWallpaperWithOpenGL(SurfaceHolder sh, int w, int h, int left, int top) { - if (!initGL(sh)) return false; - - final float right = left + mBackground.getWidth() * mScale; - final float bottom = top + mBackground.getHeight() * mScale; - - final Rect frame = sh.getSurfaceFrame(); - final Matrix4f ortho = new Matrix4f(); - ortho.loadOrtho(0.0f, frame.width(), frame.height(), 0.0f, -1.0f, 1.0f); - - final FloatBuffer triangleVertices = createMesh(left, top, right, bottom); - - final int texture = loadTexture(mBackground); - final int program = buildProgram(sSimpleVS, sSimpleFS); - - final int attribPosition = glGetAttribLocation(program, "position"); - final int attribTexCoords = glGetAttribLocation(program, "texCoords"); - final int uniformTexture = glGetUniformLocation(program, "texture"); - final int uniformProjection = glGetUniformLocation(program, "projection"); - - checkGlError(); - - glViewport(0, 0, frame.width(), frame.height()); - glBindTexture(GL_TEXTURE_2D, texture); - - glUseProgram(program); - glEnableVertexAttribArray(attribPosition); - glEnableVertexAttribArray(attribTexCoords); - glUniform1i(uniformTexture, 0); - glUniformMatrix4fv(uniformProjection, 1, false, ortho.getArray(), 0); - - checkGlError(); - - if (w > 0 || h > 0) { - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClear(GL_COLOR_BUFFER_BIT); - } - - // drawQuad - triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); - glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false, - TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices); - - triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); - glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false, - TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices); - - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - - boolean status = mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); - checkEglError(); - - finishGL(texture, program); - - return status; - } - - private FloatBuffer createMesh(int left, int top, float right, float bottom) { - final float[] verticesData = { - // X, Y, Z, U, V - left, bottom, 0.0f, 0.0f, 1.0f, - right, bottom, 0.0f, 1.0f, 1.0f, - left, top, 0.0f, 0.0f, 0.0f, - right, top, 0.0f, 1.0f, 0.0f, - }; - - final int bytes = verticesData.length * FLOAT_SIZE_BYTES; - final FloatBuffer triangleVertices = ByteBuffer.allocateDirect(bytes).order( - ByteOrder.nativeOrder()).asFloatBuffer(); - triangleVertices.put(verticesData).position(0); - return triangleVertices; - } - - private int loadTexture(Bitmap bitmap) { - int[] textures = new int[1]; - - glActiveTexture(GL_TEXTURE0); - glGenTextures(1, textures, 0); - checkGlError(); - - int texture = textures[0]; - glBindTexture(GL_TEXTURE_2D, texture); - checkGlError(); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0); - checkGlError(); - - return texture; - } - - private int buildProgram(String vertex, String fragment) { - int vertexShader = buildShader(vertex, GL_VERTEX_SHADER); - if (vertexShader == 0) return 0; - - int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER); - if (fragmentShader == 0) return 0; - - int program = glCreateProgram(); - glAttachShader(program, vertexShader); - glAttachShader(program, fragmentShader); - glLinkProgram(program); - checkGlError(); - - glDeleteShader(vertexShader); - glDeleteShader(fragmentShader); - - int[] status = new int[1]; - glGetProgramiv(program, GL_LINK_STATUS, status, 0); - if (status[0] != GL_TRUE) { - String error = glGetProgramInfoLog(program); - Log.d(GL_LOG_TAG, "Error while linking program:\n" + error); - glDeleteProgram(program); - return 0; - } - - return program; - } - - private int buildShader(String source, int type) { - int shader = glCreateShader(type); - - glShaderSource(shader, source); - checkGlError(); - - glCompileShader(shader); - checkGlError(); - - int[] status = new int[1]; - glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0); - if (status[0] != GL_TRUE) { - String error = glGetShaderInfoLog(shader); - Log.d(GL_LOG_TAG, "Error while compiling shader:\n" + error); - glDeleteShader(shader); - return 0; - } - - return shader; - } - - private void checkEglError() { - int error = mEgl.eglGetError(); - if (error != EGL_SUCCESS) { - Log.w(GL_LOG_TAG, "EGL error = " + GLUtils.getEGLErrorString(error)); - } - } - - private void checkGlError() { - int error = glGetError(); - if (error != GL_NO_ERROR) { - Log.w(GL_LOG_TAG, "GL error = 0x" + Integer.toHexString(error), new Throwable()); - } - } - - private void finishGL(int texture, int program) { - int[] textures = new int[1]; - textures[0] = texture; - glDeleteTextures(1, textures, 0); - glDeleteProgram(program); - mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - mEgl.eglDestroySurface(mEglDisplay, mEglSurface); - mEgl.eglDestroyContext(mEglDisplay, mEglContext); - mEgl.eglTerminate(mEglDisplay); - } - - private boolean initGL(SurfaceHolder surfaceHolder) { - mEgl = (EGL10) EGLContext.getEGL(); - - mEglDisplay = mEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (mEglDisplay == EGL_NO_DISPLAY) { - throw new RuntimeException("eglGetDisplay failed " + - GLUtils.getEGLErrorString(mEgl.eglGetError())); - } - - int[] version = new int[2]; - if (!mEgl.eglInitialize(mEglDisplay, version)) { - throw new RuntimeException("eglInitialize failed " + - GLUtils.getEGLErrorString(mEgl.eglGetError())); - } - - mEglConfig = chooseEglConfig(); - if (mEglConfig == null) { - throw new RuntimeException("eglConfig not initialized"); - } - - mEglContext = createContext(mEgl, mEglDisplay, mEglConfig); - if (mEglContext == EGL_NO_CONTEXT) { - throw new RuntimeException("createContext failed " + - GLUtils.getEGLErrorString(mEgl.eglGetError())); - } - - int attribs[] = { - EGL_WIDTH, 1, - EGL_HEIGHT, 1, - EGL_NONE - }; - EGLSurface tmpSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs); - mEgl.eglMakeCurrent(mEglDisplay, tmpSurface, tmpSurface, mEglContext); - - int[] maxSize = new int[1]; - Rect frame = surfaceHolder.getSurfaceFrame(); - glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0); - - mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - mEgl.eglDestroySurface(mEglDisplay, tmpSurface); - - if(frame.width() > maxSize[0] || frame.height() > maxSize[0]) { - mEgl.eglDestroyContext(mEglDisplay, mEglContext); - mEgl.eglTerminate(mEglDisplay); - Log.e(GL_LOG_TAG, "requested texture size " + - frame.width() + "x" + frame.height() + " exceeds the support maximum of " + - maxSize[0] + "x" + maxSize[0]); - return false; - } - - mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null); - if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) { - int error = mEgl.eglGetError(); - if (error == EGL_BAD_NATIVE_WINDOW || error == EGL_BAD_ALLOC) { - Log.e(GL_LOG_TAG, "createWindowSurface returned " + - GLUtils.getEGLErrorString(error) + "."); - return false; - } - throw new RuntimeException("createWindowSurface failed " + - GLUtils.getEGLErrorString(error)); - } - - if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { - throw new RuntimeException("eglMakeCurrent failed " + - GLUtils.getEGLErrorString(mEgl.eglGetError())); - } - - return true; - } - - - EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { - int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; - return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, attrib_list); - } - - private EGLConfig chooseEglConfig() { - int[] configsCount = new int[1]; - EGLConfig[] configs = new EGLConfig[1]; - int[] configSpec = getConfig(); - if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) { - throw new IllegalArgumentException("eglChooseConfig failed " + - GLUtils.getEGLErrorString(mEgl.eglGetError())); - } else if (configsCount[0] > 0) { - return configs[0]; - } - return null; - } - - private int[] getConfig() { - return new int[] { - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 0, - EGL_DEPTH_SIZE, 0, - EGL_STENCIL_SIZE, 0, - EGL_CONFIG_CAVEAT, EGL_NONE, - EGL_NONE - }; - } } } diff --git a/packages/SystemUI/src/com/android/systemui/Interpolators.java b/packages/SystemUI/src/com/android/systemui/Interpolators.java index b8cfa3e48c20..aeef49689517 100644 --- a/packages/SystemUI/src/com/android/systemui/Interpolators.java +++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java @@ -18,6 +18,7 @@ package com.android.systemui; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateInterpolator; +import android.view.animation.BounceInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; @@ -43,6 +44,7 @@ public class Interpolators { public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f); public static final Interpolator PANEL_CLOSE_ACCELERATED = new PathInterpolator(0.3f, 0, 0.5f, 1); + public static final Interpolator BOUNCE = new BounceInterpolator(); /** * Interpolator to be used when animating a move based on a click. Pair with enough duration. diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java index 1d55ee5ac0d6..cbb69ee98a03 100644 --- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java +++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java @@ -25,7 +25,7 @@ import android.os.PowerManager; import android.os.SystemClock; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.LatencyTracker; +import com.android.internal.util.LatencyTracker; import com.android.systemui.statusbar.phone.FingerprintUnlockController; import com.android.systemui.statusbar.phone.StatusBar; diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java new file mode 100644 index 000000000000..d0128efe2c44 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java @@ -0,0 +1,267 @@ +/* + * 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; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.graphics.Rect; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.PatternMatcher; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; +import android.view.SurfaceControl; + +import com.android.systemui.OverviewProxyService.OverviewProxyListener; +import com.android.systemui.shared.recents.IOverviewProxy; +import com.android.systemui.shared.recents.ISystemUiProxy; +import com.android.systemui.shared.system.GraphicBufferCompat; +import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.policy.CallbackController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Class to send information from overview to launcher with a binder. + */ +public class OverviewProxyService implements CallbackController<OverviewProxyListener>, Dumpable { + + public static final String TAG_OPS = "OverviewProxyService"; + public static final boolean DEBUG_OVERVIEW_PROXY = false; + private static final long BACKOFF_MILLIS = 5000; + + private final Context mContext; + private final Handler mHandler; + private final Runnable mConnectionRunnable = this::internalConnectToCurrentUser; + private final ComponentName mLauncherComponentName; + private final DeviceProvisionedController mDeviceProvisionedController + = Dependency.get(DeviceProvisionedController.class); + private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>(); + + private IOverviewProxy mOverviewProxy; + private int mConnectionBackoffAttempts; + private CharSequence mOnboardingText; + + private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { + + public GraphicBufferCompat screenshot(Rect sourceCrop, int width, int height, int minLayer, + int maxLayer, boolean useIdentityTransform, int rotation) { + long token = Binder.clearCallingIdentity(); + try { + return new GraphicBufferCompat(SurfaceControl.screenshotToBuffer(sourceCrop, width, + height, minLayer, maxLayer, useIdentityTransform, rotation)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public void startScreenPinning(int taskId) { + long token = Binder.clearCallingIdentity(); + try { + mHandler.post(() -> { + StatusBar statusBar = ((SystemUIApplication) mContext).getComponent( + StatusBar.class); + if (statusBar != null) { + statusBar.showScreenPinningRequest(taskId, false /* allowCancel */); + } + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public void onRecentsAnimationStarted() { + long token = Binder.clearCallingIdentity(); + try { + mHandler.post(OverviewProxyService.this::notifyRecentsAnimationStarted); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public void setRecentsOnboardingText(CharSequence text) { + mOnboardingText = text; + } + }; + + private final BroadcastReceiver mLauncherAddedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // Reconnect immediately, instead of waiting for resume to arrive. + startConnectionToCurrentUser(); + } + }; + + private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (service != null) { + mConnectionBackoffAttempts = 0; + mOverviewProxy = IOverviewProxy.Stub.asInterface(service); + // Listen for launcher's death + try { + service.linkToDeath(mOverviewServiceDeathRcpt, 0); + } catch (RemoteException e) { + Log.e(TAG_OPS, "Lost connection to launcher service", e); + } + try { + mOverviewProxy.onBind(mSysUiProxy); + } catch (RemoteException e) { + Log.e(TAG_OPS, "Failed to call onBind()", e); + } + notifyConnectionChanged(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + // Do nothing + } + }; + + private final DeviceProvisionedListener mDeviceProvisionedCallback = + new DeviceProvisionedListener() { + @Override + public void onUserSetupChanged() { + if (mDeviceProvisionedController.isCurrentUserSetup()) { + internalConnectToCurrentUser(); + } + } + + @Override + public void onUserSwitched() { + mConnectionBackoffAttempts = 0; + internalConnectToCurrentUser(); + } + }; + + // This is the death handler for the binder from the launcher service + private final IBinder.DeathRecipient mOverviewServiceDeathRcpt + = this::startConnectionToCurrentUser; + + public OverviewProxyService(Context context) { + mContext = context; + mHandler = new Handler(); + mConnectionBackoffAttempts = 0; + mLauncherComponentName = ComponentName + .unflattenFromString(context.getString(R.string.config_overviewServiceComponent)); + mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback); + + // Listen for the package update changes. + IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addDataScheme("package"); + filter.addDataSchemeSpecificPart(mLauncherComponentName.getPackageName(), + PatternMatcher.PATTERN_LITERAL); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + mContext.registerReceiver(mLauncherAddedReceiver, filter); + } + + public void startConnectionToCurrentUser() { + if (mHandler.getLooper() != Looper.myLooper()) { + mHandler.post(mConnectionRunnable); + } else { + internalConnectToCurrentUser(); + } + } + + private void internalConnectToCurrentUser() { + disconnectFromLauncherService(); + + // If user has not setup yet or already connected, do not try to connect + if (!mDeviceProvisionedController.isCurrentUserSetup()) { + return; + } + mHandler.removeCallbacks(mConnectionRunnable); + Intent launcherServiceIntent = new Intent(); + launcherServiceIntent.setComponent(mLauncherComponentName); + boolean bound = mContext.bindServiceAsUser(launcherServiceIntent, + mOverviewServiceConnection, Context.BIND_AUTO_CREATE, + UserHandle.of(mDeviceProvisionedController.getCurrentUser())); + if (!bound) { + // Retry after exponential backoff timeout + final long timeoutMs = (long) Math.scalb(BACKOFF_MILLIS, mConnectionBackoffAttempts); + mHandler.postDelayed(mConnectionRunnable, timeoutMs); + mConnectionBackoffAttempts++; + } + } + + @Override + public void addCallback(OverviewProxyListener listener) { + mConnectionCallbacks.add(listener); + listener.onConnectionChanged(mOverviewProxy != null); + } + + @Override + public void removeCallback(OverviewProxyListener listener) { + mConnectionCallbacks.remove(listener); + } + + public IOverviewProxy getProxy() { + return mOverviewProxy; + } + + public CharSequence getOnboardingText() { + return mOnboardingText; + } + + private void disconnectFromLauncherService() { + if (mOverviewProxy != null) { + mOverviewProxy.asBinder().unlinkToDeath(mOverviewServiceDeathRcpt, 0); + mContext.unbindService(mOverviewServiceConnection); + mOverviewProxy = null; + notifyConnectionChanged(); + } + } + + private void notifyConnectionChanged() { + for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { + mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null); + } + } + + private void notifyRecentsAnimationStarted() { + for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { + mConnectionCallbacks.get(i).onRecentsAnimationStarted(); + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(TAG_OPS + " state:"); + pw.print(" mConnectionBackoffAttempts="); pw.println(mConnectionBackoffAttempts); + pw.print(" isCurrentUserSetup="); pw.println(mDeviceProvisionedController + .isCurrentUserSetup()); + pw.print(" isConnected="); pw.println(mOverviewProxy != null); + } + + public interface OverviewProxyListener { + default void onConnectionChanged(boolean isConnected) {} + default void onRecentsAnimationStarted() {} + } +} diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index 4437d314a7c2..adb4e33d1a19 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -48,6 +48,8 @@ public final class Prefs { Key.QS_WORK_ADDED, Key.QS_NIGHTDISPLAY_ADDED, Key.SEEN_MULTI_USER, + Key.NUM_APPS_LAUNCHED, + Key.HAS_SEEN_RECENTS_ONBOARDING, }) public @interface Key { @Deprecated @@ -75,6 +77,8 @@ public final class Prefs { @Deprecated String QS_NIGHTDISPLAY_ADDED = "QsNightDisplayAdded"; String SEEN_MULTI_USER = "HasSeenMultiUser"; + String NUM_APPS_LAUNCHED = "NumAppsLaunched"; + String HAS_SEEN_RECENTS_ONBOARDING = "HasSeenRecentsOnboarding"; } public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { diff --git a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java index 44a044bc4ce4..f9dbf4a15e5c 100644 --- a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java @@ -21,14 +21,14 @@ import android.view.Display; import android.view.View; public interface RecentsComponent { - void showRecentApps(boolean triggeredFromAltTab, boolean fromHome); + void showRecentApps(boolean triggeredFromAltTab); void showNextAffiliatedTask(); void showPrevAffiliatedTask(); /** * Docks the top-most task and opens recents. */ - boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds, + boolean splitPrimaryTask(int dragMode, int stackCreateMode, Rect initialBounds, int metricsDockAction); /** diff --git a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java deleted file mode 100644 index b15b79fb984e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java +++ /dev/null @@ -1,218 +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 com.android.systemui; - -import static com.android.systemui.tuner.TunablePadding.FLAG_START; -import static com.android.systemui.tuner.TunablePadding.FLAG_END; - -import android.app.Fragment; -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.graphics.PixelFormat; -import android.provider.Settings.Secure; -import android.support.annotation.VisibleForTesting; -import android.util.DisplayMetrics; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnLayoutChangeListener; -import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; -import android.view.WindowManager; -import android.widget.ImageView; - -import com.android.systemui.R.id; -import com.android.systemui.fragments.FragmentHostManager; -import com.android.systemui.fragments.FragmentHostManager.FragmentListener; -import com.android.systemui.plugins.qs.QS; -import com.android.systemui.qs.SecureSetting; -import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; -import com.android.systemui.statusbar.phone.NavigationBarFragment; -import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.tuner.TunablePadding; -import com.android.systemui.tuner.TunerService; -import com.android.systemui.tuner.TunerService.Tunable; - -public class RoundedCorners extends SystemUI implements Tunable { - public static final String SIZE = "sysui_rounded_size"; - public static final String PADDING = "sysui_rounded_content_padding"; - - private int mRoundedDefault; - private View mOverlay; - private View mBottomOverlay; - private float mDensity; - private TunablePadding mQsPadding; - private TunablePadding mStatusBarPadding; - private TunablePadding mNavBarPadding; - - @Override - public void start() { - mRoundedDefault = mContext.getResources().getDimensionPixelSize( - R.dimen.rounded_corner_radius); - if (mRoundedDefault != 0) { - setupRounding(); - } - int padding = mContext.getResources().getDimensionPixelSize( - R.dimen.rounded_corner_content_padding); - if (padding != 0) { - setupPadding(padding); - } - } - - private void setupRounding() { - mOverlay = LayoutInflater.from(mContext) - .inflate(R.layout.rounded_corners, null); - mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); - mOverlay.setAlpha(0); - mOverlay.findViewById(R.id.right).setRotation(90); - - mContext.getSystemService(WindowManager.class) - .addView(mOverlay, getWindowLayoutParams()); - mBottomOverlay = LayoutInflater.from(mContext) - .inflate(R.layout.rounded_corners, null); - mBottomOverlay.setAlpha(0); - mBottomOverlay.findViewById(R.id.right).setRotation(180); - mBottomOverlay.findViewById(R.id.left).setRotation(270); - WindowManager.LayoutParams layoutParams = getWindowLayoutParams(); - layoutParams.gravity = Gravity.BOTTOM; - mContext.getSystemService(WindowManager.class) - .addView(mBottomOverlay, layoutParams); - - DisplayMetrics metrics = new DisplayMetrics(); - mContext.getSystemService(WindowManager.class) - .getDefaultDisplay().getMetrics(metrics); - mDensity = metrics.density; - - Dependency.get(TunerService.class).addTunable(this, SIZE); - - // Watch color inversion and invert the overlay as needed. - SecureSetting setting = new SecureSetting(mContext, Dependency.get(Dependency.MAIN_HANDLER), - Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) { - @Override - protected void handleValueChanged(int value, boolean observedChange) { - int tint = value != 0 ? Color.WHITE : Color.BLACK; - ColorStateList tintList = ColorStateList.valueOf(tint); - ((ImageView) mOverlay.findViewById(id.left)).setImageTintList(tintList); - ((ImageView) mOverlay.findViewById(id.right)).setImageTintList(tintList); - ((ImageView) mBottomOverlay.findViewById(id.left)).setImageTintList(tintList); - ((ImageView) mBottomOverlay.findViewById(id.right)).setImageTintList(tintList); - } - }; - setting.setListening(true); - setting.onChange(false); - - mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, - int oldTop, int oldRight, int oldBottom) { - mOverlay.removeOnLayoutChangeListener(this); - mOverlay.animate() - .alpha(1) - .setDuration(1000) - .start(); - mBottomOverlay.animate() - .alpha(1) - .setDuration(1000) - .start(); - } - }); - } - - private void setupPadding(int padding) { - // Add some padding to all the content near the edge of the screen. - StatusBar sb = getComponent(StatusBar.class); - View statusBar = (sb != null ? sb.getStatusBarWindow() : null); - if (statusBar != null) { - TunablePadding.addTunablePadding(statusBar.findViewById(R.id.keyguard_header), PADDING, - padding, FLAG_END); - - FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBar); - fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG, - new TunablePaddingTagListener(padding, R.id.status_bar)); - fragmentHostManager.addTagListener(QS.TAG, - new TunablePaddingTagListener(padding, R.id.header)); - } - } - - private WindowManager.LayoutParams getWindowLayoutParams() { - final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - LayoutParams.WRAP_CONTENT, - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, - WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH - | WindowManager.LayoutParams.FLAG_SLIPPERY - | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, - PixelFormat.TRANSLUCENT); - lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS - | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; - lp.setTitle("RoundedOverlay"); - lp.gravity = Gravity.TOP; - return lp; - } - - - @Override - public void onTuningChanged(String key, String newValue) { - if (mOverlay == null) return; - if (SIZE.equals(key)) { - int size = mRoundedDefault; - try { - size = (int) (Integer.parseInt(newValue) * mDensity); - } catch (Exception e) { - } - setSize(mOverlay.findViewById(R.id.left), size); - setSize(mOverlay.findViewById(R.id.right), size); - setSize(mBottomOverlay.findViewById(R.id.left), size); - setSize(mBottomOverlay.findViewById(R.id.right), size); - } - } - - private void setSize(View view, int pixelSize) { - LayoutParams params = view.getLayoutParams(); - params.width = pixelSize; - params.height = pixelSize; - view.setLayoutParams(params); - } - - @VisibleForTesting - static class TunablePaddingTagListener implements FragmentListener { - - private final int mPadding; - private final int mId; - private TunablePadding mTunablePadding; - - public TunablePaddingTagListener(int padding, int id) { - mPadding = padding; - mId = id; - } - - @Override - public void onFragmentViewCreated(String tag, Fragment fragment) { - if (mTunablePadding != null) { - mTunablePadding.destroy(); - } - View view = fragment.getView(); - if (mId != 0) { - view = view.findViewById(mId); - } - mTunablePadding = TunablePadding.addTunablePadding(view, PADDING, mPadding, - FLAG_START | FLAG_END); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java new file mode 100644 index 000000000000..0b3e9e5072aa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -0,0 +1,415 @@ +/* + * 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; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + +import static com.android.systemui.tuner.TunablePadding.FLAG_START; +import static com.android.systemui.tuner.TunablePadding.FLAG_END; + +import android.app.Fragment; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.hardware.display.DisplayManager; +import android.provider.Settings.Secure; +import android.support.annotation.VisibleForTesting; +import android.util.DisplayMetrics; +import android.view.DisplayCutout; +import android.view.DisplayInfo; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnLayoutChangeListener; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.android.systemui.fragments.FragmentHostManager; +import com.android.systemui.fragments.FragmentHostManager.FragmentListener; +import com.android.systemui.plugins.qs.QS; +import com.android.systemui.qs.SecureSetting; +import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; +import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.tuner.TunablePadding; +import com.android.systemui.tuner.TunerService; +import com.android.systemui.tuner.TunerService.Tunable; + +/** + * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout) + * for antialiasing and emulation purposes. + */ +public class ScreenDecorations extends SystemUI implements Tunable { + public static final String SIZE = "sysui_rounded_size"; + public static final String PADDING = "sysui_rounded_content_padding"; + + private int mRoundedDefault; + private View mOverlay; + private View mBottomOverlay; + private float mDensity; + private WindowManager mWindowManager; + private boolean mLandscape; + + @Override + public void start() { + mWindowManager = mContext.getSystemService(WindowManager.class); + mRoundedDefault = mContext.getResources().getDimensionPixelSize( + R.dimen.rounded_corner_radius); + if (mRoundedDefault != 0 || shouldDrawCutout()) { + setupDecorations(); + } + int padding = mContext.getResources().getDimensionPixelSize( + R.dimen.rounded_corner_content_padding); + if (padding != 0) { + setupPadding(padding); + } + } + + private void setupDecorations() { + mOverlay = LayoutInflater.from(mContext) + .inflate(R.layout.rounded_corners, null); + ((ViewGroup)mOverlay).addView(new DisplayCutoutView(mContext, true, + this::updateWindowVisibilities)); + mBottomOverlay = LayoutInflater.from(mContext) + .inflate(R.layout.rounded_corners, null); + ((ViewGroup)mBottomOverlay).addView(new DisplayCutoutView(mContext, false, + this::updateWindowVisibilities)); + + mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + mOverlay.setAlpha(0); + + mBottomOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + mBottomOverlay.setAlpha(0); + + updateViews(); + + mWindowManager.addView(mOverlay, getWindowLayoutParams()); + mWindowManager.addView(mBottomOverlay, getBottomLayoutParams()); + + DisplayMetrics metrics = new DisplayMetrics(); + mWindowManager.getDefaultDisplay().getMetrics(metrics); + mDensity = metrics.density; + + Dependency.get(TunerService.class).addTunable(this, SIZE); + + // Watch color inversion and invert the overlay as needed. + SecureSetting setting = new SecureSetting(mContext, Dependency.get(Dependency.MAIN_HANDLER), + Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) { + @Override + protected void handleValueChanged(int value, boolean observedChange) { + int tint = value != 0 ? Color.WHITE : Color.BLACK; + ColorStateList tintList = ColorStateList.valueOf(tint); + ((ImageView) mOverlay.findViewById(R.id.left)).setImageTintList(tintList); + ((ImageView) mOverlay.findViewById(R.id.right)).setImageTintList(tintList); + ((ImageView) mBottomOverlay.findViewById(R.id.left)).setImageTintList(tintList); + ((ImageView) mBottomOverlay.findViewById(R.id.right)).setImageTintList(tintList); + } + }; + setting.setListening(true); + setting.onChange(false); + + mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, + int oldTop, int oldRight, int oldBottom) { + mOverlay.removeOnLayoutChangeListener(this); + mOverlay.animate() + .alpha(1) + .setDuration(1000) + .start(); + mBottomOverlay.animate() + .alpha(1) + .setDuration(1000) + .start(); + } + }); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + boolean newLanscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE; + if (newLanscape != mLandscape) { + mLandscape = newLanscape; + + if (mOverlay != null) { + updateLayoutParams(); + updateViews(); + } + } + if (shouldDrawCutout() && mOverlay == null) { + setupDecorations(); + } + } + + private void updateViews() { + View topLeft = mOverlay.findViewById(R.id.left); + View topRight = mOverlay.findViewById(R.id.right); + View bottomLeft = mBottomOverlay.findViewById(R.id.left); + View bottomRight = mBottomOverlay.findViewById(R.id.right); + if (mLandscape) { + // Flip corners + View tmp = topRight; + topRight = bottomLeft; + bottomLeft = tmp; + } + updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0); + updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90); + updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270); + updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180); + + updateWindowVisibilities(); + } + + private void updateView(View v, int gravity, int rotation) { + ((FrameLayout.LayoutParams)v.getLayoutParams()).gravity = gravity; + v.setRotation(rotation); + } + + private void updateWindowVisibilities() { + updateWindowVisibility(mOverlay); + updateWindowVisibility(mBottomOverlay); + } + + private void updateWindowVisibility(View overlay) { + boolean visibleForCutout = shouldDrawCutout() + && overlay.findViewById(R.id.display_cutout).getVisibility() == View.VISIBLE; + boolean visibleForRoundedCorners = mRoundedDefault > 0; + overlay.setVisibility(visibleForCutout || visibleForRoundedCorners + ? View.VISIBLE : View.GONE); + } + + private boolean shouldDrawCutout() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout); + } + + private void setupPadding(int padding) { + // Add some padding to all the content near the edge of the screen. + StatusBar sb = getComponent(StatusBar.class); + View statusBar = (sb != null ? sb.getStatusBarWindow() : null); + if (statusBar != null) { + TunablePadding.addTunablePadding(statusBar.findViewById(R.id.keyguard_header), PADDING, + padding, FLAG_END); + + FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBar); + fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG, + new TunablePaddingTagListener(padding, R.id.status_bar)); + fragmentHostManager.addTagListener(QS.TAG, + new TunablePaddingTagListener(padding, R.id.header)); + } + } + + @VisibleForTesting + WindowManager.LayoutParams getWindowLayoutParams() { + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH + | WindowManager.LayoutParams.FLAG_SLIPPERY + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, + PixelFormat.TRANSLUCENT); + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS + | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; + lp.setTitle("ScreenDecorOverlay"); + lp.gravity = Gravity.TOP | Gravity.LEFT; + lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + if (mLandscape) { + lp.width = WRAP_CONTENT; + lp.height = MATCH_PARENT; + } + return lp; + } + + private WindowManager.LayoutParams getBottomLayoutParams() { + WindowManager.LayoutParams lp = getWindowLayoutParams(); + lp.setTitle("ScreenDecorOverlayBottom"); + lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; + return lp; + } + + private void updateLayoutParams() { + mWindowManager.updateViewLayout(mOverlay, getWindowLayoutParams()); + mWindowManager.updateViewLayout(mBottomOverlay, getBottomLayoutParams()); + } + + @Override + public void onTuningChanged(String key, String newValue) { + if (mOverlay == null) return; + if (SIZE.equals(key)) { + int size = mRoundedDefault; + try { + size = (int) (Integer.parseInt(newValue) * mDensity); + } catch (Exception e) { + } + setSize(mOverlay.findViewById(R.id.left), size); + setSize(mOverlay.findViewById(R.id.right), size); + setSize(mBottomOverlay.findViewById(R.id.left), size); + setSize(mBottomOverlay.findViewById(R.id.right), size); + } + } + + private void setSize(View view, int pixelSize) { + LayoutParams params = view.getLayoutParams(); + params.width = pixelSize; + params.height = pixelSize; + view.setLayoutParams(params); + } + + @VisibleForTesting + static class TunablePaddingTagListener implements FragmentListener { + + private final int mPadding; + private final int mId; + private TunablePadding mTunablePadding; + + public TunablePaddingTagListener(int padding, int id) { + mPadding = padding; + mId = id; + } + + @Override + public void onFragmentViewCreated(String tag, Fragment fragment) { + if (mTunablePadding != null) { + mTunablePadding.destroy(); + } + View view = fragment.getView(); + if (mId != 0) { + view = view.findViewById(mId); + } + mTunablePadding = TunablePadding.addTunablePadding(view, PADDING, mPadding, + FLAG_START | FLAG_END); + } + } + + public static class DisplayCutoutView extends View implements DisplayManager.DisplayListener { + + private final DisplayInfo mInfo = new DisplayInfo(); + private final Paint mPaint = new Paint(); + private final Rect mBoundingRect = new Rect(); + private final Path mBoundingPath = new Path(); + private final int[] mLocation = new int[2]; + private final boolean mStart; + private final Runnable mVisibilityChangedListener; + + public DisplayCutoutView(Context context, boolean start, + Runnable visibilityChangedListener) { + super(context); + mStart = start; + mVisibilityChangedListener = visibilityChangedListener; + setId(R.id.display_cutout); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mContext.getSystemService(DisplayManager.class).registerDisplayListener(this, + getHandler()); + update(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + getLocationOnScreen(mLocation); + canvas.translate(-mLocation[0], -mLocation[1]); + if (!mBoundingPath.isEmpty()) { + mPaint.setColor(Color.BLACK); + mPaint.setStyle(Paint.Style.FILL); + canvas.drawPath(mBoundingPath, mPaint); + } + } + + @Override + public void onDisplayAdded(int displayId) { + } + + @Override + public void onDisplayRemoved(int displayId) { + } + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == getDisplay().getDisplayId()) { + update(); + } + } + + private void update() { + requestLayout(); + getDisplay().getDisplayInfo(mInfo); + mBoundingRect.setEmpty(); + mBoundingPath.reset(); + int newVisible; + if (hasCutout()) { + mBoundingRect.set(mInfo.displayCutout.getBoundingRect()); + mInfo.displayCutout.getBounds().getBoundaryPath(mBoundingPath); + newVisible = VISIBLE; + } else { + newVisible = GONE; + } + if (newVisible != getVisibility()) { + setVisibility(newVisible); + mVisibilityChangedListener.run(); + } + } + + private boolean hasCutout() { + if (mInfo.displayCutout == null) { + return false; + } + DisplayCutout displayCutout = mInfo.displayCutout.calculateRelativeTo( + new Rect(0, 0, mInfo.logicalWidth, mInfo.logicalHeight)); + if (mStart) { + return displayCutout.getSafeInsetLeft() > 0 + || displayCutout.getSafeInsetTop() > 0; + } else { + return displayCutout.getSafeInsetRight() > 0 + || displayCutout.getSafeInsetBottom() > 0; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mBoundingRect.isEmpty()) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + setMeasuredDimension( + resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0), + resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0)); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java new file mode 100644 index 000000000000..302face14c1a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java @@ -0,0 +1,88 @@ +/* + * 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; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.slice.SliceManager; +import android.app.slice.SliceProvider; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface.OnDismissListener; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.widget.CheckBox; +import android.widget.TextView; + +public class SlicePermissionActivity extends Activity implements OnClickListener, + OnDismissListener { + + private static final String TAG = "SlicePermissionActivity"; + + private CheckBox mAllCheckbox; + + private Uri mUri; + private String mCallingPkg; + private String mProviderPkg; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mUri = getIntent().getParcelableExtra(SliceProvider.EXTRA_BIND_URI); + mCallingPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PKG); + mProviderPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PROVIDER_PKG); + + try { + PackageManager pm = getPackageManager(); + CharSequence app1 = pm.getApplicationInfo(mCallingPkg, 0).loadLabel(pm); + CharSequence app2 = pm.getApplicationInfo(mProviderPkg, 0).loadLabel(pm); + AlertDialog dialog = new AlertDialog.Builder(this) + .setTitle(getString(R.string.slice_permission_title, app1, app2)) + .setView(R.layout.slice_permission_request) + .setNegativeButton(R.string.slice_permission_deny, this) + .setPositiveButton(R.string.slice_permission_allow, this) + .setOnDismissListener(this) + .show(); + TextView t1 = dialog.getWindow().getDecorView().findViewById(R.id.text1); + t1.setText(getString(R.string.slice_permission_text_1, app2)); + TextView t2 = dialog.getWindow().getDecorView().findViewById(R.id.text2); + t2.setText(getString(R.string.slice_permission_text_2, app2)); + mAllCheckbox = dialog.getWindow().getDecorView().findViewById( + R.id.slice_permission_checkbox); + mAllCheckbox.setText(getString(R.string.slice_permission_checkbox, app1)); + } catch (NameNotFoundException e) { + Log.e(TAG, "Couldn't find package", e); + finish(); + } + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + getSystemService(SliceManager.class).grantPermissionFromUser(mUri, mCallingPkg, + mAllCheckbox.isChecked()); + } + finish(); + } + + @Override + public void onDismiss(DialogInterface dialog) { + finish(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 4b377153e558..a64ce296c76c 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; import android.graphics.RectF; @@ -83,7 +84,6 @@ public class SwipeHelper implements Gefingerpoken { private boolean mMenuRowIntercepting; private boolean mLongPressSent; - private LongPressListener mLongPressListener; private Runnable mWatchLongPress; private final long mLongPressTimeout; @@ -115,10 +115,6 @@ public class SwipeHelper implements Gefingerpoken { mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f); } - public void setLongPressListener(LongPressListener listener) { - mLongPressListener = listener; - } - public void setDensityScale(float densityScale) { mDensityScale = densityScale; } @@ -257,7 +253,7 @@ public class SwipeHelper implements Gefingerpoken { } } - public void removeLongPressCallback() { + public void cancelLongPress() { if (mWatchLongPress != null) { mHandler.removeCallbacks(mWatchLongPress); mWatchLongPress = null; @@ -288,33 +284,27 @@ public class SwipeHelper implements Gefingerpoken { mInitialTouchPos = getPos(ev); mPerpendicularInitialTouchPos = getPerpendicularPos(ev); mTranslation = getTranslation(mCurrView); - if (mLongPressListener != null) { - if (mWatchLongPress == null) { - mWatchLongPress = new Runnable() { - @Override - public void run() { - if (mCurrView != null && !mLongPressSent) { - mLongPressSent = true; + if (mWatchLongPress == null) { + mWatchLongPress = new Runnable() { + @Override + public void run() { + if (mCurrView != null && !mLongPressSent) { + mLongPressSent = true; + mCurrView.getLocationOnScreen(mTmpPos); + final int x = (int) ev.getRawX() - mTmpPos[0]; + final int y = (int) ev.getRawY() - mTmpPos[1]; + if (mCurrView instanceof ExpandableNotificationRow) { mCurrView.sendAccessibilityEvent( AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); - mCurrView.getLocationOnScreen(mTmpPos); - final int x = (int) ev.getRawX() - mTmpPos[0]; - final int y = (int) ev.getRawY() - mTmpPos[1]; - MenuItem menuItem = null; - if (mCurrView instanceof ExpandableNotificationRow) { - menuItem = ((ExpandableNotificationRow) mCurrView) - .getProvider().getLongpressMenuItem(mContext); - } - if (menuItem != null) { - mLongPressListener.onLongPress(mCurrView, x, y, - menuItem); - } + ExpandableNotificationRow currRow = + (ExpandableNotificationRow) mCurrView; + currRow.doLongClickCallback(x, y); } } - }; - } - mHandler.postDelayed(mWatchLongPress, mLongPressTimeout); + } + }; } + mHandler.postDelayed(mWatchLongPress, mLongPressTimeout); } break; @@ -327,11 +317,13 @@ public class SwipeHelper implements Gefingerpoken { float deltaPerpendicular = perpendicularPos - mPerpendicularInitialTouchPos; if (Math.abs(delta) > mPagingTouchSlop && Math.abs(delta) > Math.abs(deltaPerpendicular)) { - mCallback.onBeginDrag(mCurrView); - mDragging = true; - mInitialTouchPos = getPos(ev); - mTranslation = getTranslation(mCurrView); - removeLongPressCallback(); + if (mCallback.canChildBeDragged(mCurrView)) { + mCallback.onBeginDrag(mCurrView); + mDragging = true; + mInitialTouchPos = getPos(ev); + mTranslation = getTranslation(mCurrView); + } + cancelLongPress(); } } break; @@ -343,7 +335,7 @@ public class SwipeHelper implements Gefingerpoken { mCurrView = null; mLongPressSent = false; mMenuRowIntercepting = false; - removeLongPressCallback(); + cancelLongPress(); if (captured) return true; break; } @@ -586,7 +578,7 @@ public class SwipeHelper implements Gefingerpoken { // We are not doing anything, make sure the long press callback // is not still ticking like a bomb waiting to go off. - removeLongPressCallback(); + cancelLongPress(); return false; } } @@ -733,16 +725,10 @@ public class SwipeHelper implements Gefingerpoken { * @return The factor the falsing threshold should be multiplied with */ float getFalsingThresholdFactor(); - } - /** - * Equivalent to View.OnLongClickListener with coordinates - */ - public interface LongPressListener { /** - * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates - * @return whether the longpress was handled + * @return If true, the given view is draggable. */ - boolean onLongPress(View v, int x, int y, MenuItem item); + default boolean canChildBeDragged(@NonNull View animView) { return true; } } } diff --git a/packages/SystemUI/src/com/android/systemui/SysUIToast.java b/packages/SystemUI/src/com/android/systemui/SysUIToast.java index 89bc82f87930..43b918dbea73 100644 --- a/packages/SystemUI/src/com/android/systemui/SysUIToast.java +++ b/packages/SystemUI/src/com/android/systemui/SysUIToast.java @@ -15,13 +15,19 @@ */ package com.android.systemui; +import android.annotation.StringRes; import android.content.Context; import android.view.WindowManager; import android.widget.Toast; +import static android.widget.Toast.Duration; public class SysUIToast { - public static Toast makeText(Context context, CharSequence text, int duration) { + public static Toast makeText(Context context, @StringRes int resId, @Duration int duration) { + return makeText(context, context.getString(resId), duration); + } + + public static Toast makeText(Context context, CharSequence text, @Duration int duration) { Toast toast = Toast.makeText(context, text, duration); toast.getWindowParams().privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 9adafda7adf5..2c0e95b5af26 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -37,9 +37,7 @@ import com.android.systemui.keyboard.KeyboardUI; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.media.RingtonePlayer; import com.android.systemui.pip.PipUI; -import com.android.systemui.plugins.GlobalActions; import com.android.systemui.plugins.OverlayPlugin; -import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.PluginManager; import com.android.systemui.power.PowerUI; @@ -54,6 +52,7 @@ import com.android.systemui.util.NotificationChannels; import com.android.systemui.util.leak.GarbageMonitor; import com.android.systemui.volume.VolumeUI; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -62,48 +61,13 @@ import java.util.Map; */ public class SystemUIApplication extends Application implements SysUiServiceProvider { - private static final String TAG = "SystemUIService"; + public static final String TAG = "SystemUIService"; private static final boolean DEBUG = false; /** - * The classes of the stuff to start. - */ - private final Class<?>[] SERVICES = new Class[] { - Dependency.class, - NotificationChannels.class, - CommandQueue.CommandQueueStart.class, - KeyguardViewMediator.class, - Recents.class, - VolumeUI.class, - Divider.class, - SystemBars.class, - StorageNotification.class, - PowerUI.class, - RingtonePlayer.class, - KeyboardUI.class, - PipUI.class, - ShortcutKeyDispatcher.class, - VendorServices.class, - GarbageMonitor.Service.class, - LatencyTester.class, - GlobalActionsComponent.class, - RoundedCorners.class, - }; - - /** - * The classes of the stuff to start for each user. This is a subset of the services listed - * above. - */ - private final Class<?>[] SERVICES_PER_USER = new Class[] { - Dependency.class, - NotificationChannels.class, - Recents.class - }; - - /** * Hold a reference on the stuff we start. */ - private final SystemUI[] mServices = new SystemUI[SERVICES.length]; + private SystemUI[] mServices; private boolean mServicesStarted; private boolean mBootCompleted; private final Map<Class<?>, Object> mComponents = new HashMap<>(); @@ -149,7 +113,7 @@ public class SystemUIApplication extends Application implements SysUiServiceProv // been broadcasted on startup for the primary SystemUI process. Instead, for // components which require the SystemUI component to be initialized per-user, we // start those components now for the current non-system user. - startServicesIfNeeded(SERVICES_PER_USER); + startSecondaryUserServicesIfNeeded(); } } @@ -161,7 +125,8 @@ public class SystemUIApplication extends Application implements SysUiServiceProv */ public void startServicesIfNeeded() { - startServicesIfNeeded(SERVICES); + String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents); + startServicesIfNeeded(names); } /** @@ -171,13 +136,16 @@ public class SystemUIApplication extends Application implements SysUiServiceProv * <p>This method must only be called from the main thread.</p> */ void startSecondaryUserServicesIfNeeded() { - startServicesIfNeeded(SERVICES_PER_USER); + String[] names = + getResources().getStringArray(R.array.config_systemUIServiceComponentsPerUser); + startServicesIfNeeded(names); } - private void startServicesIfNeeded(Class<?>[] services) { + private void startServicesIfNeeded(String[] services) { if (mServicesStarted) { return; } + mServices = new SystemUI[services.length]; if (!mBootCompleted) { // check to see if maybe it was already completed long before we began @@ -195,14 +163,16 @@ public class SystemUIApplication extends Application implements SysUiServiceProv log.traceBegin("StartServices"); final int N = services.length; for (int i = 0; i < N; i++) { - Class<?> cl = services[i]; - if (DEBUG) Log.d(TAG, "loading: " + cl); - log.traceBegin("StartServices" + cl.getSimpleName()); + String clsName = services[i]; + if (DEBUG) Log.d(TAG, "loading: " + clsName); + log.traceBegin("StartServices" + clsName); long ti = System.currentTimeMillis(); + Class cls; try { - - Object newService = SystemUIFactory.getInstance().createInstance(cl); - mServices[i] = (SystemUI) ((newService == null) ? cl.newInstance() : newService); + cls = Class.forName(clsName); + mServices[i] = (SystemUI) cls.newInstance(); + } catch(ClassNotFoundException ex){ + throw new RuntimeException(ex); } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } catch (InstantiationException ex) { @@ -218,7 +188,7 @@ public class SystemUIApplication extends Application implements SysUiServiceProv // Warn if initialization of component takes too long ti = System.currentTimeMillis() - ti; if (ti > 1000) { - Log.w(TAG, "Initialization of " + cl.getName() + " took " + ti + " ms"); + Log.w(TAG, "Initialization of " + cls.getName() + " took " + ti + " ms"); } if (mBootCompleted) { mServices[i].onBootCompleted(); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 0c067ff38295..d1834e921d20 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -16,29 +16,43 @@ package com.android.systemui; +import android.app.AlarmManager; import android.content.Context; import android.util.ArrayMap; import android.util.Log; import android.view.View; import android.view.ViewGroup; +import com.android.internal.logging.MetricsLogger; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.Dependency.DependencyProvider; import com.android.systemui.keyguard.DismissCallbackRegistry; +import com.android.systemui.qs.QSTileHost; import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.statusbar.NotificationEntryManager; +import com.android.systemui.statusbar.NotificationGutsManager; +import com.android.systemui.statusbar.NotificationListener; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationLogger; +import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.ScrimView; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LockIcon; import com.android.systemui.statusbar.phone.LockscreenWallpaper; +import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationIconAreaController; -import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.qs.QSTileHost; import com.android.systemui.statusbar.phone.ScrimController; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.volume.VolumeDialogControllerImpl; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.SmartReplyConstants; import java.util.function.Consumer; @@ -86,10 +100,10 @@ public class SystemUIFactory { public ScrimController createScrimController(LightBarController lightBarController, ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim, - LockscreenWallpaper lockscreenWallpaper, - Consumer<Boolean> scrimVisibleListener) { + LockscreenWallpaper lockscreenWallpaper, Consumer<Integer> scrimVisibleListener, + DozeParameters dozeParameters, AlarmManager alarmManager) { return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim, - scrimVisibleListener); + scrimVisibleListener, dozeParameters, alarmManager); } public NotificationIconAreaController createNotificationIconAreaController(Context context, @@ -107,10 +121,22 @@ public class SystemUIFactory { return new QSTileHost(context, statusBar, iconController); } - public <T> T createInstance(Class<T> classType) { - return null; - } - public void injectDependencies(ArrayMap<Object, DependencyProvider> providers, - Context context) { } + Context context) { + providers.put(NotificationLockscreenUserManager.class, + () -> new NotificationLockscreenUserManager(context)); + providers.put(VisualStabilityManager.class, VisualStabilityManager::new); + providers.put(NotificationGroupManager.class, NotificationGroupManager::new); + providers.put(NotificationMediaManager.class, () -> new NotificationMediaManager(context)); + providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(context)); + providers.put(NotificationRemoteInputManager.class, + () -> new NotificationRemoteInputManager(context)); + providers.put(SmartReplyConstants.class, + () -> new SmartReplyConstants(Dependency.get(Dependency.MAIN_HANDLER), context)); + providers.put(NotificationListener.class, () -> new NotificationListener(context)); + providers.put(NotificationLogger.class, NotificationLogger::new); + providers.put(NotificationViewHierarchyManager.class, + () -> new NotificationViewHierarchyManager(context)); + providers.put(NotificationEntryManager.class, () -> new NotificationEntryManager(context)); + } } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java index 11e0f289e6a4..ddf0bd0cbab4 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java @@ -20,11 +20,15 @@ import android.app.Service; import android.content.Intent; import android.os.Build; import android.os.IBinder; +import android.os.Process; import android.os.SystemProperties; +import android.util.Slog; import java.io.FileDescriptor; import java.io.PrintWriter; +import com.android.internal.os.BinderInternal; + public class SystemUIService extends Service { @Override @@ -36,6 +40,21 @@ public class SystemUIService extends Service { if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) { throw new RuntimeException(); } + + if (Build.IS_DEBUGGABLE) { + // b/71353150 - looking for leaked binder proxies + BinderInternal.nSetBinderProxyCountEnabled(true); + BinderInternal.nSetBinderProxyCountWatermarks(1000,900); + BinderInternal.setBinderProxyCountCallback( + new BinderInternal.BinderProxyLimitListener() { + @Override + public void onLimitReached(int uid) { + Slog.w(SystemUIApplication.TAG, + "uid " + uid + " sent too many Binder proxies to uid " + + Process.myUid()); + } + }, Dependency.get(Dependency.MAIN_HANDLER)); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java b/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java index 931a99415615..69e347c9476d 100644 --- a/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java +++ b/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java @@ -463,4 +463,8 @@ public class DataCollector implements SensorEventListener { public boolean isReportingEnabled() { return mAllowReportRejectedTouch; } + + public void onFalsingSessionStarted() { + sessionEntrypoint(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java new file mode 100644 index 000000000000..a89a8ef5b70e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java @@ -0,0 +1,54 @@ +/* + * 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.car; + +import android.content.Context; +import android.service.notification.StatusBarNotification; + +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.NotificationEntryManager; + +public class CarNotificationEntryManager extends NotificationEntryManager { + public CarNotificationEntryManager(Context context) { + super(context); + } + + /** + * Returns the + * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will + * be triggered when a notification card is long-pressed. + */ + @Override + public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() { + // For the automative use case, we do not want to the user to be able to interact with + // a notification other than a regular click. As a result, just return null for the + // long click listener. + return null; + } + + @Override + public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) { + // Because space is usually constrained in the auto use-case, there should not be a + // pinned notification when the shade has been expanded. Ensure this by not pinning any + // notification if the shade is already opened. + if (!mPresenter.isPresenterFullyCollapsed()) { + return false; + } + + return super.shouldPeek(entry, sbn); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java index 5a19e7dc60ed..245d240017e1 100644 --- a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java @@ -20,8 +20,7 @@ import android.util.ArrayMap; import com.android.systemui.Dependency.DependencyProvider; import com.android.systemui.SystemUIFactory; -import com.android.systemui.plugins.VolumeDialogController; -import com.android.systemui.volume.car.CarVolumeDialogController; +import com.android.systemui.statusbar.NotificationEntryManager; /** * Class factory to provide car specific SystemUI components. @@ -31,6 +30,7 @@ public class CarSystemUIFactory extends SystemUIFactory { public void injectDependencies(ArrayMap<Object, DependencyProvider> providers, Context context) { super.injectDependencies(providers, context); - providers.put(VolumeDialogController.class, () -> new CarVolumeDialogController(context)); + providers.put(NotificationEntryManager.class, + () -> new CarNotificationEntryManager(context)); } } diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java new file mode 100644 index 000000000000..afc9629ecede --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java @@ -0,0 +1,216 @@ +/* + * 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.charging; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.PixelFormat; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.util.Slog; +import android.view.Gravity; +import android.view.View; +import android.view.WindowManager; + +/** + * A WirelessChargingAnimation is a view containing view + animation for wireless charging. + * @hide + */ +public class WirelessChargingAnimation { + + public static final long DURATION = 1133; + private static final String TAG = "WirelessChargingView"; + private static final boolean LOCAL_LOGV = false; + + private final WirelessChargingView mCurrentWirelessChargingView; + private static WirelessChargingView mPreviousWirelessChargingView; + + public interface Callback { + void onAnimationStarting(); + void onAnimationEnded(); + } + + /** + * Constructs an empty WirelessChargingAnimation object. If looper is null, + * Looper.myLooper() is used. Must set + * {@link WirelessChargingAnimation#mCurrentWirelessChargingView} + * before calling {@link #show} - can be done through {@link #makeWirelessChargingAnimation}. + * @hide + */ + public WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper, int + batteryLevel, Callback callback) { + mCurrentWirelessChargingView = new WirelessChargingView(context, looper, + batteryLevel, callback); + } + + /** + * Creates a wireless charging animation object populated with next view. + * @hide + */ + public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context, + @Nullable Looper looper, int batteryLevel, Callback callback) { + return new WirelessChargingAnimation(context, looper, batteryLevel, callback); + } + + /** + * Show the view for the specified duration. + */ + public void show() { + if (mCurrentWirelessChargingView == null || + mCurrentWirelessChargingView.mNextView == null) { + throw new RuntimeException("setView must have been called"); + } + + if (mPreviousWirelessChargingView != null) { + mPreviousWirelessChargingView.hide(0); + } + + mPreviousWirelessChargingView = mCurrentWirelessChargingView; + mCurrentWirelessChargingView.show(); + mCurrentWirelessChargingView.hide(DURATION); + } + + private static class WirelessChargingView { + private static final int SHOW = 0; + private static final int HIDE = 1; + + private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); + private final Handler mHandler; + + private int mGravity; + + private View mView; + private View mNextView; + private WindowManager mWM; + private Callback mCallback; + + public WirelessChargingView(Context context, @Nullable Looper looper, int batteryLevel, + Callback callback) { + mCallback = callback; + mNextView = new WirelessChargingLayout(context, batteryLevel); + mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER; + + final WindowManager.LayoutParams params = mParams; + params.height = WindowManager.LayoutParams.WRAP_CONTENT; + params.width = WindowManager.LayoutParams.WRAP_CONTENT; + params.format = PixelFormat.TRANSLUCENT; + + params.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; + params.setTitle("Charging Animation"); + params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_DIM_BEHIND; + + params.dimAmount = .3f; + + if (looper == null) { + // Use Looper.myLooper() if looper is not specified. + looper = Looper.myLooper(); + if (looper == null) { + throw new RuntimeException( + "Can't display wireless animation on a thread that has not called " + + "Looper.prepare()"); + } + } + + mHandler = new Handler(looper, null) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case SHOW: { + handleShow(); + break; + } + case HIDE: { + handleHide(); + // Don't do this in handleHide() because it is also invoked by + // handleShow() + mNextView = null; + break; + } + } + } + }; + } + + public void show() { + if (LOCAL_LOGV) Log.v(TAG, "SHOW: " + this); + mHandler.obtainMessage(SHOW).sendToTarget(); + } + + public void hide(long duration) { + mHandler.removeMessages(HIDE); + + if (LOCAL_LOGV) Log.v(TAG, "HIDE: " + this); + mHandler.sendMessageDelayed(Message.obtain(mHandler, HIDE), duration); + } + + private void handleShow() { + if (LOCAL_LOGV) { + Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView=" + + mNextView); + } + + if (mView != mNextView) { + // remove the old view if necessary + handleHide(); + mView = mNextView; + Context context = mView.getContext().getApplicationContext(); + String packageName = mView.getContext().getOpPackageName(); + if (context == null) { + context = mView.getContext(); + } + mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mParams.packageName = packageName; + mParams.hideTimeoutMilliseconds = DURATION; + + if (mView.getParent() != null) { + if (LOCAL_LOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); + mWM.removeView(mView); + } + if (LOCAL_LOGV) Log.v(TAG, "ADD! " + mView + " in " + this); + + try { + if (mCallback != null) { + mCallback.onAnimationStarting(); + } + mWM.addView(mView, mParams); + } catch (WindowManager.BadTokenException e) { + Slog.d(TAG, "Unable to add wireless charging view. " + e); + } + } + } + + private void handleHide() { + if (LOCAL_LOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView); + if (mView != null) { + if (mView.getParent() != null) { + if (LOCAL_LOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); + if (mCallback != null) { + mCallback.onAnimationEnded(); + } + mWM.removeViewImmediate(mView); + } + + mView = null; + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java new file mode 100644 index 000000000000..8f87d647a0e3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java @@ -0,0 +1,126 @@ +/* + * 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.charging; + +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.util.AttributeSet; +import android.view.animation.PathInterpolator; +import android.widget.FrameLayout; +import android.widget.TextView; + +import com.android.systemui.Interpolators; +import com.android.systemui.R; + +import java.text.NumberFormat; + +/** + * @hide + */ +public class WirelessChargingLayout extends FrameLayout { + private final static int UNKNOWN_BATTERY_LEVEL = -1; + + public WirelessChargingLayout(Context context) { + super(context); + init(context, null); + } + + public WirelessChargingLayout(Context context, int batterylLevel) { + super(context); + init(context, null, batterylLevel); + } + + public WirelessChargingLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + private void init(Context c, AttributeSet attrs) { + init(c, attrs, -1); + } + + private void init(Context context, AttributeSet attrs, int batteryLevel) { + final int mBatteryLevel = batteryLevel; + inflate(context, R.layout.wireless_charging_layout, this); + + // where the circle animation occurs: + final WirelessChargingView mChargingView = findViewById(R.id.wireless_charging_view); + + // amount of battery: + final TextView mPercentage = findViewById(R.id.wireless_charging_percentage); + + // (optional) time until full charge if available + final TextView mSecondaryText = findViewById(R.id.wireless_charging_secondary_text); + + if (batteryLevel != UNKNOWN_BATTERY_LEVEL) { + mPercentage.setText(NumberFormat.getPercentInstance().format(mBatteryLevel / 100f)); + mPercentage.setAlpha(0); + } + + final long chargingAnimationFadeStartOffset = (long) context.getResources().getInteger( + R.integer.wireless_charging_fade_offset); + final long chargingAnimationFadeDuration = (long) context.getResources().getInteger( + R.integer.wireless_charging_fade_duration); + final int batteryLevelTextSizeStart = context.getResources().getDimensionPixelSize( + R.dimen.wireless_charging_anim_battery_level_text_size_start); + final int batteryLevelTextSizeEnd = context.getResources().getDimensionPixelSize( + R.dimen.wireless_charging_anim_battery_level_text_size_end); + + // Animation Scale: battery percentage text scales from 0% to 100% + ValueAnimator textSizeAnimator = ObjectAnimator.ofFloat(mPercentage, "textSize", + batteryLevelTextSizeStart, batteryLevelTextSizeEnd); + textSizeAnimator.setInterpolator(new PathInterpolator(0, 0, 0, 1)); + textSizeAnimator.setDuration((long) context.getResources().getInteger( + R.integer.wireless_charging_battery_level_text_scale_animation_duration)); + + // Animation Opacity: battery percentage text transitions from 0 to 1 opacity + ValueAnimator textOpacityAnimator = ObjectAnimator.ofFloat(mPercentage, "alpha", 0, 1); + textOpacityAnimator.setInterpolator(Interpolators.LINEAR); + textOpacityAnimator.setDuration((long) context.getResources().getInteger( + R.integer.wireless_charging_battery_level_text_opacity_duration)); + textOpacityAnimator.setStartDelay((long) context.getResources().getInteger( + R.integer.wireless_charging_anim_opacity_offset)); + + // Animation Opacity: battery percentage text fades from 1 to 0 opacity + ValueAnimator textFadeAnimator = ObjectAnimator.ofFloat(mPercentage, "alpha", 1, 0); + textFadeAnimator.setDuration(chargingAnimationFadeDuration); + textFadeAnimator.setInterpolator(Interpolators.LINEAR); + textFadeAnimator.setStartDelay(chargingAnimationFadeStartOffset); + + // Animation Opacity: wireless charging circle animation fades from 1 to 0 opacity + ValueAnimator circleFadeAnimator = ObjectAnimator.ofFloat(mChargingView, "alpha", + 1, 0); + circleFadeAnimator.setDuration(chargingAnimationFadeDuration); + circleFadeAnimator.setInterpolator(Interpolators.LINEAR); + circleFadeAnimator.setStartDelay(chargingAnimationFadeStartOffset); + + // Animation Opacity: secondary text animation fades from 1 to 0 opacity + ValueAnimator secondaryTextFadeAnimator = ObjectAnimator.ofFloat(mSecondaryText, "alpha", + 1, 0); + circleFadeAnimator.setDuration(chargingAnimationFadeDuration); + secondaryTextFadeAnimator.setInterpolator(Interpolators.LINEAR); + secondaryTextFadeAnimator.setStartDelay(chargingAnimationFadeStartOffset); + + // play all animations together + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator, + circleFadeAnimator, secondaryTextFadeAnimator); + animatorSet.start(); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java new file mode 100644 index 000000000000..19c6dc1ceb1f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java @@ -0,0 +1,178 @@ +/* + * 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.charging; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.Interpolator; + +import com.android.settingslib.Utils; +import com.android.systemui.Interpolators; +import com.android.systemui.R; + +final class WirelessChargingView extends View { + + private Interpolator mInterpolator; + private float mPathGone; + private float mInterpolatedPathGone; + private long mAnimationStartTime; + private long mScaleDotsDuration; + + private boolean mFinishedAnimatingDots = false; + private int mNumDots; + + private double mAngleOffset; + private double mCurrAngleOffset; + + private int mDotsRadiusStart; + private int mDotsRadiusEnd; + private int mCurrDotRadius; + + private int mMainCircleStartRadius; + private int mMainCircleEndRadius; + private int mMainCircleCurrentRadius; + + private int mCenterX; + private int mCenterY; + + private Paint mPaint; + private Context mContext; + + public WirelessChargingView(Context context) { + super(context); + init(context, null); + } + + public WirelessChargingView(Context context, AttributeSet attr) { + super(context, attr); + init(context, attr); + } + + public WirelessChargingView(Context context, AttributeSet attr, int styleAttr) { + super(context, attr, styleAttr); + init(context, attr); + } + + public void init(Context context, AttributeSet attr) { + mContext = context; + + mDotsRadiusStart = context.getResources().getDimensionPixelSize( + R.dimen.wireless_charging_dots_radius_start); + mDotsRadiusEnd = context.getResources().getDimensionPixelSize( + R.dimen.wireless_charging_dots_radius_end); + + mMainCircleStartRadius = context.getResources().getDimensionPixelSize( + R.dimen.wireless_charging_circle_radius_start); + mMainCircleEndRadius = context.getResources().getDimensionPixelSize( + R.dimen.wireless_charging_circle_radius_end); + mMainCircleCurrentRadius = mMainCircleStartRadius; + + mAngleOffset = context.getResources().getInteger(R.integer.wireless_charging_angle_offset); + mScaleDotsDuration = (long) context.getResources().getInteger( + R.integer.wireless_charging_scale_dots_duration); + mNumDots = context.getResources().getInteger(R.integer.wireless_charging_num_dots); + + setupPaint(); + mInterpolator = Interpolators.LINEAR_OUT_SLOW_IN; + } + + private void setupPaint() { + mPaint = new Paint(); + mPaint.setColor(Utils.getColorAttr(mContext, R.attr.wallpaperTextColor)); + } + + @Override + protected void onDraw(final Canvas canvas) { + super.onDraw(canvas); + + if (mAnimationStartTime == 0) { + mAnimationStartTime = System.currentTimeMillis(); + } + + updateDrawingParameters(); + drawCircles(canvas); + + if (!mFinishedAnimatingDots) { + invalidate(); + } + } + + /** + * Draws a larger circle of radius {@link WirelessChargingView#mMainCircleEndRadius} composed of + * {@link WirelessChargingView#mNumDots} smaller circles + * @param canvas + */ + private void drawCircles(Canvas canvas) { + mCenterX = canvas.getWidth() / 2; + mCenterY = canvas.getHeight() / 2; + + // draws mNumDots to compose a larger, main circle + for (int circle = 0; circle < mNumDots; circle++) { + double angle = ((mCurrAngleOffset) * Math.PI / 180) + (circle * ((2 * Math.PI) + / mNumDots)); + + int x = (int) (mCenterX + Math.cos(angle) * (mMainCircleCurrentRadius + + mCurrDotRadius)); + int y = (int) (mCenterY + Math.sin(angle) * (mMainCircleCurrentRadius + + mCurrDotRadius)); + + canvas.drawCircle(x, y, mCurrDotRadius, mPaint); + } + + if (mMainCircleCurrentRadius >= mMainCircleEndRadius) { + mFinishedAnimatingDots = true; + } + } + + private void updateDrawingParameters() { + long now = System.currentTimeMillis(); + long timeSinceStart = now - mAnimationStartTime; + mPathGone = getPathGone(now); + mInterpolatedPathGone = mInterpolator.getInterpolation(mPathGone); + + // Position Dots: update main circle radius (that the dots compose) + if (mPathGone < 1.0f) { + mMainCircleCurrentRadius = mMainCircleStartRadius + (int) (mInterpolatedPathGone * + (mMainCircleEndRadius - mMainCircleStartRadius)); + } else { + mMainCircleCurrentRadius = mMainCircleEndRadius; + } + + // Scale Dots: update dot radius + if (timeSinceStart < mScaleDotsDuration) { + mCurrDotRadius = mDotsRadiusStart + (int) (mInterpolator.getInterpolation((float) + timeSinceStart / mScaleDotsDuration) * (mDotsRadiusEnd - mDotsRadiusStart)); + } else { + mCurrDotRadius = mDotsRadiusEnd; + } + + // Rotation Dot Group: dots rotate from 0 to 20 degrees + mCurrAngleOffset = mAngleOffset * mPathGone; + } + + /** + * @return decimal depicting how far along the creation of the larger circle (of circles) is + * For values < 1.0, the larger circle is being drawn + * For values > 1.0 the larger circle has been drawn and further animation can occur + */ + private float getPathGone(long now) { + return (float) (now - mAnimationStartTime) / (WirelessChargingAnimation.DURATION); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java index e4b405f580d4..ed659e2d16d5 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java @@ -167,6 +167,9 @@ public class FalsingManager implements SensorEventListener { if (mDataCollector.isEnabledFull()) { registerSensors(COLLECTOR_SENSORS); } + if (mDataCollector.isEnabled()) { + mDataCollector.onFalsingSessionStarted(); + } } private void registerSensors(int [] sensors) { diff --git a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java index debda2109637..8515bf2e2968 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java @@ -30,8 +30,6 @@ import android.util.Log; import com.android.systemui.R; -import java.util.Arrays; - /** * Class to store the policy for AOD, which comes from * {@link android.provider.Settings.Global} @@ -42,12 +40,16 @@ public class AlwaysOnDisplayPolicy { private static final long DEFAULT_PROX_SCREEN_OFF_DELAY_MS = 10 * DateUtils.SECOND_IN_MILLIS; private static final long DEFAULT_PROX_COOLDOWN_TRIGGER_MS = 2 * DateUtils.SECOND_IN_MILLIS; private static final long DEFAULT_PROX_COOLDOWN_PERIOD_MS = 5 * DateUtils.SECOND_IN_MILLIS; + private static final long DEFAULT_WALLPAPER_VISIBILITY_MS = 60 * DateUtils.SECOND_IN_MILLIS; + private static final long DEFAULT_WALLPAPER_FADE_OUT_MS = 400; static final String KEY_SCREEN_BRIGHTNESS_ARRAY = "screen_brightness_array"; static final String KEY_DIMMING_SCRIM_ARRAY = "dimming_scrim_array"; static final String KEY_PROX_SCREEN_OFF_DELAY_MS = "prox_screen_off_delay"; static final String KEY_PROX_COOLDOWN_TRIGGER_MS = "prox_cooldown_trigger"; static final String KEY_PROX_COOLDOWN_PERIOD_MS = "prox_cooldown_period"; + static final String KEY_WALLPAPER_VISIBILITY_MS = "wallpaper_visibility_timeout"; + static final String KEY_WALLPAPER_FADE_OUT_MS = "wallpaper_fade_out_duration"; /** * Integer array to map ambient brightness type to real screen brightness. @@ -91,6 +93,24 @@ public class AlwaysOnDisplayPolicy { */ public long proxCooldownPeriodMs; + /** + * For how long(ms) the wallpaper should still be visible + * after entering AoD. + * + * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS + * @see #KEY_WALLPAPER_VISIBILITY_MS + */ + public long wallpaperVisibilityDuration; + + /** + * Duration(ms) of the fade out animation after + * {@link #KEY_WALLPAPER_VISIBILITY_MS} elapses. + * + * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS + * @see #KEY_WALLPAPER_FADE_OUT_MS + */ + public long wallpaperFadeOutDuration; + private final KeyValueListParser mParser; private final Context mContext; private SettingsObserver mSettingsObserver; @@ -102,20 +122,6 @@ public class AlwaysOnDisplayPolicy { mSettingsObserver.observe(); } - private int[] parseIntArray(final String key, final int[] defaultArray) { - final String value = mParser.getString(key, null); - if (value != null) { - try { - return Arrays.stream(value.split(":")).map(String::trim).mapToInt( - Integer::parseInt).toArray(); - } catch (NumberFormatException e) { - return defaultArray; - } - } else { - return defaultArray; - } - } - private final class SettingsObserver extends ContentObserver { private final Uri ALWAYS_ON_DISPLAY_CONSTANTS_URI = Settings.Global.getUriFor(Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS); @@ -154,10 +160,14 @@ public class AlwaysOnDisplayPolicy { DEFAULT_PROX_COOLDOWN_TRIGGER_MS); proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS, DEFAULT_PROX_COOLDOWN_PERIOD_MS); - screenBrightnessArray = parseIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY, + wallpaperFadeOutDuration = mParser.getLong(KEY_WALLPAPER_FADE_OUT_MS, + DEFAULT_WALLPAPER_FADE_OUT_MS); + wallpaperVisibilityDuration = mParser.getLong(KEY_WALLPAPER_VISIBILITY_MS, + DEFAULT_WALLPAPER_VISIBILITY_MS); + screenBrightnessArray = mParser.getIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY, resources.getIntArray( R.array.config_doze_brightness_sensor_to_brightness)); - dimmingScrimArray = parseIntArray(KEY_DIMMING_SCRIM_ARRAY, + dimmingScrimArray = mParser.getIntArray(KEY_DIMMING_SCRIM_ARRAY, resources.getIntArray( R.array.config_doze_brightness_sensor_to_scrim_opacity)); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index 6f8bcff16a83..092f3d241d6c 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -63,9 +63,10 @@ public class DozeFactory { new DozeFalsingManagerAdapter(FalsingManager.getInstance(context)), createDozeTriggers(context, sensorManager, host, alarmManager, config, params, handler, wakeLock, machine), - createDozeUi(context, host, wakeLock, machine, handler, alarmManager), - new DozeScreenState(wrappedService, handler), + createDozeUi(context, host, wakeLock, machine, handler, alarmManager, params), + new DozeScreenState(wrappedService, handler, params), createDozeScreenBrightness(context, wrappedService, sensorManager, host, handler), + new DozeWallpaperState(context) }); return machine; @@ -89,8 +90,9 @@ public class DozeFactory { } private DozeMachine.Part createDozeUi(Context context, DozeHost host, WakeLock wakeLock, - DozeMachine machine, Handler handler, AlarmManager alarmManager) { - return new DozeUi(context, alarmManager, machine, wakeLock, host, handler); + DozeMachine machine, Handler handler, AlarmManager alarmManager, + DozeParameters params) { + return new DozeUi(context, alarmManager, machine, wakeLock, host, handler, params); } public static DozeHost getHost(DozeService service) { diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java index 7db118d7fb1c..6a29299872a4 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java @@ -35,10 +35,10 @@ public interface DozeHost { boolean isBlockingDoze(); void startPendingIntentDismissingKeyguard(PendingIntent intent); - void abortPulsing(); void extendPulse(); void setAnimateWakeup(boolean animateWakeup); + void setAnimateScreenOff(boolean animateScreenOff); void onDoubleTap(float x, float y); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index 8ec6afc326e1..6ff8e3db25e5 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -24,6 +24,7 @@ import android.view.Display; import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.internal.util.Preconditions; +import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.Assert; import com.android.systemui.util.wakelock.WakeLock; @@ -87,12 +88,11 @@ public class DozeMachine { } } - int screenState() { + int screenState(DozeParameters parameters) { switch (this) { case UNINITIALIZED: case INITIALIZED: case DOZE: - case DOZE_REQUEST_PULSE: case DOZE_AOD_PAUSED: return Display.STATE_OFF; case DOZE_PULSING: @@ -100,6 +100,9 @@ public class DozeMachine { case DOZE_AOD: case DOZE_AOD_PAUSING: return Display.STATE_DOZE_SUSPEND; + case DOZE_REQUEST_PULSE: + return parameters.getDisplayNeedsBlanking() ? Display.STATE_OFF + : Display.STATE_ON; default: return Display.STATE_UNKNOWN; } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java index bef9cb38180f..3053de366be5 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java @@ -19,6 +19,8 @@ package com.android.systemui.doze; import android.os.Handler; import android.view.Display; +import com.android.systemui.statusbar.phone.DozeParameters; + /** * Controls the screen when dozing. */ @@ -26,17 +28,20 @@ public class DozeScreenState implements DozeMachine.Part { private final DozeMachine.Service mDozeService; private final Handler mHandler; private final Runnable mApplyPendingScreenState = this::applyPendingScreenState; + private final DozeParameters mParameters; private int mPendingScreenState = Display.STATE_UNKNOWN; - public DozeScreenState(DozeMachine.Service service, Handler handler) { + public DozeScreenState(DozeMachine.Service service, Handler handler, + DozeParameters parameters) { mDozeService = service; mHandler = handler; + mParameters = parameters; } @Override public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { - int screenState = newState.screenState(); + int screenState = newState.screenState(mParameters); if (newState == DozeMachine.State.FINISH) { // Make sure not to apply the screen state after DozeService was destroyed. diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java index 5d0a9d70e99d..03b0082b96e7 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java @@ -33,8 +33,10 @@ public class DozeScreenStatePreventingAdapter extends DozeMachine.Service.Delega @Override public void setDozeScreenState(int state) { - if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) { + if (state == Display.STATE_DOZE) { state = Display.STATE_ON; + } else if (state == Display.STATE_DOZE_SUSPEND) { + state = Display.STATE_ON_SUSPEND; } super.setDozeScreenState(state); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java index 98b110619a1c..34d392861d7c 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java @@ -24,9 +24,10 @@ import android.util.Log; import com.android.systemui.Dependency; import com.android.systemui.plugins.DozeServicePlugin; -import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.DozeServicePlugin.RequestDoze; import com.android.systemui.plugins.PluginListener; +import com.android.systemui.plugins.PluginManager; + import java.io.FileDescriptor; import java.io.PrintWriter; @@ -92,6 +93,7 @@ public class DozeService extends DreamService @Override protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) { + super.dumpOnHandler(fd, pw, args); if (mDozeMachine != null) { mDozeMachine.dump(pw); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java index 851b78cfcd49..75f1b501b3f4 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java @@ -16,6 +16,8 @@ package com.android.systemui.doze; +import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED; + import android.app.AlarmManager; import android.content.Context; import android.os.Handler; @@ -23,6 +25,7 @@ import android.os.SystemClock; import android.text.format.Formatter; import android.util.Log; +import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.AlarmTimeout; import com.android.systemui.util.wakelock.WakeLock; @@ -41,18 +44,22 @@ public class DozeUi implements DozeMachine.Part { private final WakeLock mWakeLock; private final DozeMachine mMachine; private final AlarmTimeout mTimeTicker; + private final boolean mCanAnimateWakeup; private long mLastTimeTickElapsed = 0; public DozeUi(Context context, AlarmManager alarmManager, DozeMachine machine, - WakeLock wakeLock, DozeHost host, Handler handler) { + WakeLock wakeLock, DozeHost host, Handler handler, + DozeParameters params) { mContext = context; mMachine = machine; mWakeLock = wakeLock; mHost = host; mHandler = handler; + mCanAnimateWakeup = !params.getDisplayNeedsBlanking(); mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler); + mHost.setAnimateScreenOff(params.getCanControlScreenOffAnimation()); } private void pulseWhileDozing(int reason) { @@ -74,6 +81,11 @@ public class DozeUi implements DozeMachine.Part { public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { switch (newState) { case DOZE_AOD: + if (oldState == DOZE_AOD_PAUSED) { + mHost.dozeTimeTick(); + } + scheduleTimeTick(); + break; case DOZE_AOD_PAUSING: scheduleTimeTick(); break; @@ -106,7 +118,7 @@ public class DozeUi implements DozeMachine.Part { // Keep current state. break; default: - mHost.setAnimateWakeup(false); + mHost.setAnimateWakeup(mCanAnimateWakeup); break; } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java new file mode 100644 index 000000000000..5156272b1ade --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java @@ -0,0 +1,106 @@ +/* + * 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.doze; + +import android.app.IWallpaperManager; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; + +import java.io.PrintWriter; + +/** + * Propagates doze state to wallpaper engine. + */ +public class DozeWallpaperState implements DozeMachine.Part { + + private static final String TAG = "DozeWallpaperState"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final IWallpaperManager mWallpaperManagerService; + private boolean mKeyguardVisible; + private boolean mIsAmbientMode; + private final DozeParameters mDozeParameters; + + public DozeWallpaperState(Context context) { + this(IWallpaperManager.Stub.asInterface( + ServiceManager.getService(Context.WALLPAPER_SERVICE)), + new DozeParameters(context), KeyguardUpdateMonitor.getInstance(context)); + } + + @VisibleForTesting + DozeWallpaperState(IWallpaperManager wallpaperManagerService, DozeParameters parameters, + KeyguardUpdateMonitor keyguardUpdateMonitor) { + mWallpaperManagerService = wallpaperManagerService; + mDozeParameters = parameters; + keyguardUpdateMonitor.registerCallback(new KeyguardUpdateMonitorCallback() { + @Override + public void onKeyguardVisibilityChanged(boolean showing) { + mKeyguardVisible = showing; + } + }); + } + + @Override + public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { + final boolean isAmbientMode; + switch (newState) { + case DOZE_AOD: + case DOZE_AOD_PAUSING: + case DOZE_AOD_PAUSED: + case DOZE_REQUEST_PULSE: + case DOZE_PULSING: + case DOZE_PULSE_DONE: + isAmbientMode = mDozeParameters.getAlwaysOn(); + break; + default: + isAmbientMode = false; + } + + final boolean animated; + if (isAmbientMode) { + animated = mDozeParameters.getCanControlScreenOffAnimation() && !mKeyguardVisible; + } else { + animated = !mDozeParameters.getDisplayNeedsBlanking(); + } + + if (isAmbientMode != mIsAmbientMode) { + mIsAmbientMode = isAmbientMode; + try { + Log.i(TAG, "AoD wallpaper state changed to: " + mIsAmbientMode + + ", animated: " + animated); + mWallpaperManagerService.setInAmbientMode(mIsAmbientMode, animated); + } catch (RemoteException e) { + // Cannot notify wallpaper manager service, but it's fine, let's just skip it. + Log.w(TAG, "Cannot notify state to WallpaperManagerService: " + mIsAmbientMode); + } + } + } + + @Override + public void dump(PrintWriter pw) { + pw.println("DozeWallpaperState:"); + pw.println(" isAmbientMode: " + mIsAmbientMode); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java new file mode 100644 index 000000000000..262c71ae0b1e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java @@ -0,0 +1,220 @@ +/* + * 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.fingerprint; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.hardware.fingerprint.FingerprintDialog; +import android.hardware.fingerprint.IFingerprintDialogReceiver; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; +import android.view.WindowManager; + +import com.android.internal.os.SomeArgs; +import com.android.systemui.SystemUI; +import com.android.systemui.statusbar.CommandQueue; + +public class FingerprintDialogImpl extends SystemUI implements CommandQueue.Callbacks { + private static final String TAG = "FingerprintDialogImpl"; + private static final boolean DEBUG = true; + + protected static final int MSG_SHOW_DIALOG = 1; + protected static final int MSG_FINGERPRINT_AUTHENTICATED = 2; + protected static final int MSG_FINGERPRINT_HELP = 3; + protected static final int MSG_FINGERPRINT_ERROR = 4; + protected static final int MSG_HIDE_DIALOG = 5; + protected static final int MSG_BUTTON_NEGATIVE = 6; + protected static final int MSG_USER_CANCELED = 7; + protected static final int MSG_BUTTON_POSITIVE = 8; + protected static final int MSG_CLEAR_MESSAGE = 9; + + + private FingerprintDialogView mDialogView; + private WindowManager mWindowManager; + private IFingerprintDialogReceiver mReceiver; + private boolean mDialogShowing; + + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case MSG_SHOW_DIALOG: + handleShowDialog((SomeArgs) msg.obj); + break; + case MSG_FINGERPRINT_AUTHENTICATED: + handleFingerprintAuthenticated(); + break; + case MSG_FINGERPRINT_HELP: + handleFingerprintHelp((String) msg.obj); + break; + case MSG_FINGERPRINT_ERROR: + handleFingerprintError((String) msg.obj); + break; + case MSG_HIDE_DIALOG: + handleHideDialog((Boolean) msg.obj); + break; + case MSG_BUTTON_NEGATIVE: + handleButtonNegative(); + break; + case MSG_USER_CANCELED: + handleUserCanceled(); + break; + case MSG_BUTTON_POSITIVE: + handleButtonPositive(); + break; + case MSG_CLEAR_MESSAGE: + handleClearMessage(); + break; + } + } + }; + + @Override + public void start() { + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { + return; + } + getComponent(CommandQueue.class).addCallbacks(this); + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + mDialogView = new FingerprintDialogView(mContext, mHandler); + } + + @Override + public void showFingerprintDialog(Bundle bundle, IFingerprintDialogReceiver receiver) { + if (DEBUG) Log.d(TAG, "showFingerprintDialog"); + // Remove these messages as they are part of the previous client + mHandler.removeMessages(MSG_FINGERPRINT_ERROR); + mHandler.removeMessages(MSG_FINGERPRINT_HELP); + mHandler.removeMessages(MSG_FINGERPRINT_AUTHENTICATED); + SomeArgs args = SomeArgs.obtain(); + args.arg1 = bundle; + args.arg2 = receiver; + mHandler.obtainMessage(MSG_SHOW_DIALOG, args).sendToTarget(); + } + + @Override + public void onFingerprintAuthenticated() { + if (DEBUG) Log.d(TAG, "onFingerprintAuthenticated"); + mHandler.obtainMessage(MSG_FINGERPRINT_AUTHENTICATED).sendToTarget(); + } + + @Override + public void onFingerprintHelp(String message) { + if (DEBUG) Log.d(TAG, "onFingerprintHelp: " + message); + mHandler.obtainMessage(MSG_FINGERPRINT_HELP, message).sendToTarget(); + } + + @Override + public void onFingerprintError(String error) { + if (DEBUG) Log.d(TAG, "onFingerprintError: " + error); + mHandler.obtainMessage(MSG_FINGERPRINT_ERROR, error).sendToTarget(); + } + + @Override + public void hideFingerprintDialog() { + if (DEBUG) Log.d(TAG, "hideFingerprintDialog"); + mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget(); + } + + private void handleShowDialog(SomeArgs args) { + if (DEBUG) Log.d(TAG, "handleShowDialog"); + if (mDialogShowing) { + Log.w(TAG, "Dialog already showing"); + return; + } + mReceiver = (IFingerprintDialogReceiver) args.arg2; + mDialogView.setBundle((Bundle)args.arg1); + mWindowManager.addView(mDialogView, mDialogView.getLayoutParams()); + mDialogShowing = true; + } + + private void handleFingerprintAuthenticated() { + if (DEBUG) Log.d(TAG, "handleFingerprintAuthenticated"); + handleHideDialog(false /* userCanceled */); + } + + private void handleFingerprintHelp(String message) { + if (DEBUG) Log.d(TAG, "handleFingerprintHelp: " + message); + mDialogView.showHelpMessage(message); + } + + private void handleFingerprintError(String error) { + if (DEBUG) Log.d(TAG, "handleFingerprintError: " + error); + if (!mDialogShowing) { + if (DEBUG) Log.d(TAG, "Dialog already dismissed"); + return; + } + mDialogView.showErrorMessage(error); + } + + private void handleHideDialog(boolean userCanceled) { + if (DEBUG) Log.d(TAG, "handleHideDialog"); + if (!mDialogShowing) { + // This can happen if there's a race and we get called from both + // onAuthenticated and onError, etc. + Log.w(TAG, "Dialog already dismissed, userCanceled: " + userCanceled); + return; + } + if (userCanceled) { + try { + mReceiver.onDialogDismissed(FingerprintDialog.DISMISSED_REASON_USER_CANCEL); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException when hiding dialog", e); + } + } + mReceiver = null; + mWindowManager.removeView(mDialogView); + mDialogShowing = false; + } + + private void handleButtonNegative() { + if (mReceiver == null) { + Log.e(TAG, "Receiver is null"); + return; + } + try { + mReceiver.onDialogDismissed(FingerprintDialog.DISMISSED_REASON_NEGATIVE); + } catch (RemoteException e) { + Log.e(TAG, "Remote exception when handling negative button", e); + } + handleHideDialog(false /* userCanceled */); + } + + private void handleButtonPositive() { + if (mReceiver == null) { + Log.e(TAG, "Receiver is null"); + return; + } + try { + mReceiver.onDialogDismissed(FingerprintDialog.DISMISSED_REASON_POSITIVE); + } catch (RemoteException e) { + Log.e(TAG, "Remote exception when handling positive button", e); + } + handleHideDialog(false /* userCanceled */); + } + + private void handleClearMessage() { + mDialogView.clearMessage(); + } + + private void handleUserCanceled() { + handleHideDialog(true /* userCanceled */); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java new file mode 100644 index 000000000000..9779937aed99 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java @@ -0,0 +1,233 @@ +/* + * 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.fingerprint; + +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.graphics.PixelFormat; +import android.graphics.drawable.Drawable; +import android.hardware.fingerprint.FingerprintDialog; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.animation.Interpolator; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.systemui.Interpolators; +import com.android.systemui.R; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.PackageManagerWrapper; + +/** + * This class loads the view for the system-provided dialog. The view consists of: + * Application Icon, Title, Subtitle, Description, Fingerprint Icon, Error/Help message area, + * and positive/negative buttons. + */ +public class FingerprintDialogView extends LinearLayout { + + private static final String TAG = "FingerprintDialogView"; + + private static final int ANIMATION_DURATION = 250; // ms + + private final IBinder mWindowToken = new Binder(); + private final ActivityManagerWrapper mActivityManagerWrapper; + private final PackageManagerWrapper mPackageManageWrapper; + private final Interpolator mLinearOutSlowIn; + private final Interpolator mFastOutLinearIn; + private final float mAnimationTranslationOffset; + + private ViewGroup mLayout; + private final TextView mErrorText; + private Handler mHandler; + private Bundle mBundle; + private final LinearLayout mDialog; + + public FingerprintDialogView(Context context, Handler handler) { + super(context); + mHandler = handler; + mActivityManagerWrapper = ActivityManagerWrapper.getInstance(); + mPackageManageWrapper = PackageManagerWrapper.getInstance(); + mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN; + mFastOutLinearIn = Interpolators.FAST_OUT_LINEAR_IN; + mAnimationTranslationOffset = getResources() + .getDimension(R.dimen.fingerprint_dialog_animation_translation_offset); + + // Create the dialog + LayoutInflater factory = LayoutInflater.from(getContext()); + mLayout = (ViewGroup) factory.inflate(R.layout.fingerprint_dialog, this, false); + addView(mLayout); + + mDialog = mLayout.findViewById(R.id.dialog); + + mErrorText = mLayout.findViewById(R.id.error); + + mLayout.setOnKeyListener(new View.OnKeyListener() { + boolean downPressed = false; + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode != KeyEvent.KEYCODE_BACK) { + return false; + } + if (event.getAction() == KeyEvent.ACTION_DOWN && downPressed == false) { + downPressed = true; + } else if (event.getAction() == KeyEvent.ACTION_DOWN) { + downPressed = false; + } else if (event.getAction() == KeyEvent.ACTION_UP && downPressed == true) { + downPressed = false; + mHandler.obtainMessage(FingerprintDialogImpl.MSG_USER_CANCELED).sendToTarget(); + } + return true; + } + }); + + final View space = mLayout.findViewById(R.id.space); + final Button negative = mLayout.findViewById(R.id.button2); + final Button positive = mLayout.findViewById(R.id.button1); + + space.setClickable(true); + space.setOnTouchListener((View view, MotionEvent event) -> { + mHandler.obtainMessage(FingerprintDialogImpl.MSG_HIDE_DIALOG, true /* userCanceled*/) + .sendToTarget(); + return true; + }); + + negative.setOnClickListener((View v) -> { + mHandler.obtainMessage(FingerprintDialogImpl.MSG_BUTTON_NEGATIVE).sendToTarget(); + }); + + positive.setOnClickListener((View v) -> { + mHandler.obtainMessage(FingerprintDialogImpl.MSG_BUTTON_POSITIVE).sendToTarget(); + }); + + mLayout.setFocusableInTouchMode(true); + mLayout.requestFocus(); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + final TextView title = mLayout.findViewById(R.id.title); + final TextView subtitle = mLayout.findViewById(R.id.subtitle); + final TextView description = mLayout.findViewById(R.id.description); + final Button negative = mLayout.findViewById(R.id.button2); + final ImageView image = mLayout.findViewById(R.id.icon); + final Button positive = mLayout.findViewById(R.id.button1); + final ImageView fingerprint_icon = mLayout.findViewById(R.id.fingerprint_icon); + + title.setText(mBundle.getCharSequence(FingerprintDialog.KEY_TITLE)); + title.setSelected(true); + subtitle.setText(mBundle.getCharSequence(FingerprintDialog.KEY_SUBTITLE)); + description.setText(mBundle.getCharSequence(FingerprintDialog.KEY_DESCRIPTION)); + negative.setText(mBundle.getCharSequence(FingerprintDialog.KEY_NEGATIVE_TEXT)); + setAppIcon(image); + + final CharSequence positiveText = + mBundle.getCharSequence(FingerprintDialog.KEY_POSITIVE_TEXT); + positive.setText(positiveText); // needs to be set for marquee to work + if (positiveText != null) { + positive.setVisibility(View.VISIBLE); + } else { + positive.setVisibility(View.GONE); + } + + // Dim the background and slide the dialog up + mDialog.setTranslationY(mAnimationTranslationOffset); + mLayout.setAlpha(0f); + postOnAnimation(new Runnable() { + @Override + public void run() { + mLayout.animate() + .alpha(1f) + .setDuration(ANIMATION_DURATION) + .setInterpolator(mLinearOutSlowIn) + .withLayer() + .start(); + mDialog.animate() + .translationY(0) + .setDuration(ANIMATION_DURATION) + .setInterpolator(mLinearOutSlowIn) + .withLayer() + .start(); + } + }); + } + + public void setBundle(Bundle bundle) { + mBundle = bundle; + } + + protected void clearMessage() { + mErrorText.setVisibility(View.INVISIBLE); + } + + private void showMessage(String message) { + mHandler.removeMessages(FingerprintDialogImpl.MSG_CLEAR_MESSAGE); + mErrorText.setText(message); + mErrorText.setContentDescription(message); + mErrorText.setVisibility(View.VISIBLE); + mHandler.sendMessageDelayed(mHandler.obtainMessage(FingerprintDialogImpl.MSG_CLEAR_MESSAGE), + FingerprintDialog.HIDE_DIALOG_DELAY); + } + + public void showHelpMessage(String message) { + showMessage(message); + } + + public void showErrorMessage(String error) { + showMessage(error); + mHandler.sendMessageDelayed(mHandler.obtainMessage(FingerprintDialogImpl.MSG_HIDE_DIALOG, + false /* userCanceled */), FingerprintDialog.HIDE_DIALOG_DELAY); + } + + private void setAppIcon(ImageView image) { + final ActivityManager.RunningTaskInfo taskInfo = mActivityManagerWrapper.getRunningTask(); + final ComponentName cn = taskInfo.topActivity; + final int userId = mActivityManagerWrapper.getCurrentUserId(); + final ActivityInfo activityInfo = mPackageManageWrapper.getActivityInfo(cn, userId); + image.setImageDrawable(mActivityManagerWrapper.getBadgedActivityIcon(activityInfo, userId)); + image.setContentDescription( + getResources().getString(R.string.accessibility_fingerprint_dialog_app_icon) + + " " + + mActivityManagerWrapper.getBadgedActivityLabel(activityInfo, userId)); + } + + public WindowManager.LayoutParams getLayoutParams() { + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, + PixelFormat.TRANSLUCENT); + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; + lp.setTitle("FingerprintDialogView"); + lp.token = mWindowToken; + return lp; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java index 09a08f09fc9a..aa085626b6c2 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java @@ -14,6 +14,10 @@ package com.android.systemui.globalactions; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; + import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.Dependency; import com.android.systemui.SysUiServiceProvider; @@ -25,12 +29,9 @@ import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.ExtensionController.Extension; -import android.content.Context; -import android.os.RemoteException; -import android.os.ServiceManager; - public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager { + private GlobalActions mPlugin; private Extension<GlobalActions> mExtension; private IStatusBarService mBarService; @@ -41,10 +42,19 @@ public class GlobalActionsComponent extends SystemUI implements Callbacks, Globa mExtension = Dependency.get(ExtensionController.class).newExtension(GlobalActions.class) .withPlugin(GlobalActions.class) .withDefault(() -> new GlobalActionsImpl(mContext)) + .withCallback(this::onExtensionCallback) .build(); + mPlugin = mExtension.get(); SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this); } + private void onExtensionCallback(GlobalActions newPlugin) { + if (mPlugin != null) { + mPlugin.destroy(); + } + mPlugin = newPlugin; + } + @Override public void handleShowShutdownUi(boolean isReboot, String reason) { mExtension.get().showShutdownUi(isReboot, reason); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 33d5617c0397..1aea5e7f4a80 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -14,35 +14,22 @@ package com.android.systemui.globalactions; -import com.android.internal.R; -import com.android.internal.colorextraction.ColorExtractor; -import com.android.internal.colorextraction.ColorExtractor.GradientColors; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.util.EmergencyAffordanceManager; -import com.android.internal.telephony.TelephonyIntents; -import com.android.internal.telephony.TelephonyProperties; -import com.android.internal.widget.LockPatternUtils; -import com.android.systemui.Dependency; -import com.android.systemui.HardwareUiLayout; -import com.android.systemui.Interpolators; -import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; -import com.android.systemui.statusbar.notification.NotificationUtils; -import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.volume.VolumeDialogMotion.LogAccelerateInterpolator; -import com.android.systemui.volume.VolumeDialogMotion.LogDecelerateInterpolator; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; import android.app.ActivityManager; import android.app.Dialog; +import android.app.KeyguardManager; import android.app.WallpaperManager; +import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.graphics.Point; @@ -51,7 +38,9 @@ import android.media.AudioManager; import android.net.ConnectivityManager; import android.os.Build; import android.os.Handler; +import android.os.IBinder; import android.os.Message; +import android.os.Messenger; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; @@ -67,7 +56,6 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; -import android.util.MathUtils; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; @@ -84,7 +72,24 @@ import android.widget.ImageView.ScaleType; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.internal.R; +import com.android.internal.colorextraction.ColorExtractor; +import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.colorextraction.drawable.GradientDrawable; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.telephony.TelephonyProperties; +import com.android.internal.util.EmergencyAffordanceManager; +import com.android.internal.util.ScreenshotHelper; +import com.android.internal.widget.LockPatternUtils; +import com.android.systemui.Dependency; +import com.android.systemui.HardwareUiLayout; +import com.android.systemui.Interpolators; +import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; +import com.android.systemui.statusbar.phone.ScrimController; +import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolator; import java.util.ArrayList; import java.util.List; @@ -94,7 +99,8 @@ import java.util.List; * may show depending on whether the keyguard is showing, and whether the device * is provisioned. */ -class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { +class GlobalActionsDialog implements DialogInterface.OnDismissListener, + DialogInterface.OnClickListener { static public final String SYSTEM_DIALOG_REASON_KEY = "reason"; static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions"; @@ -115,11 +121,16 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist"; private static final String GLOBAL_ACTION_KEY_ASSIST = "assist"; private static final String GLOBAL_ACTION_KEY_RESTART = "restart"; + private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout"; + private static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot"; private final Context mContext; private final GlobalActionsManager mWindowManagerFuncs; private final AudioManager mAudioManager; private final IDreamManager mDreamManager; + private final DevicePolicyManager mDevicePolicyManager; + private final LockPatternUtils mLockPatternUtils; + private final KeyguardManager mKeyguardManager; private ArrayList<Action> mItems; private ActionsDialog mDialog; @@ -135,8 +146,11 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn private boolean mIsWaitingForEcmExit = false; private boolean mHasTelephony; private boolean mHasVibrator; + private boolean mHasLogoutButton; + private boolean mHasLockdownButton; private final boolean mShowSilentToggle; private final EmergencyAffordanceManager mEmergencyAffordanceManager; + private final ScreenshotHelper mScreenshotHelper; /** * @param context everything needs a context :( @@ -147,6 +161,10 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); mDreamManager = IDreamManager.Stub.asInterface( ServiceManager.getService(DreamService.DREAM_SERVICE)); + mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService( + Context.DEVICE_POLICY_SERVICE); + mLockPatternUtils = new LockPatternUtils(mContext); + mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -173,6 +191,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn R.bool.config_useFixedVolume); mEmergencyAffordanceManager = new EmergencyAffordanceManager(context); + mScreenshotHelper = new ScreenshotHelper(context); } /** @@ -193,6 +212,14 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn } } + /** + * Dismiss the global actions dialog, if it's currently shown + */ + public void dismissDialog() { + mHandler.removeMessages(MESSAGE_DISMISS); + mHandler.sendEmptyMessage(MESSAGE_DISMISS); + } + private void awakenIfNecessary() { if (mDreamManager != null) { try { @@ -284,6 +311,8 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn R.array.config_globalActionsList); ArraySet<String> addedKeys = new ArraySet<String>(); + mHasLogoutButton = false; + mHasLockdownButton = false; for (int i = 0; i < defaultActions.length; i++) { String actionKey = defaultActions[i]; if (addedKeys.contains(actionKey)) { @@ -310,13 +339,26 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) { mItems.add(getSettingsAction()); } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) { - mItems.add(getLockdownAction()); + if (Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0) != 0 + && shouldDisplayLockdown()) { + mItems.add(getLockdownAction()); + mHasLockdownButton = true; + } } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) { mItems.add(getVoiceAssistAction()); } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) { mItems.add(getAssistAction()); } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) { mItems.add(new RestartAction()); + } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) { + mItems.add(new ScreenshotAction()); + } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) { + if (mDevicePolicyManager.isLogoutEnabled() + && getCurrentUser().id != UserHandle.USER_SYSTEM) { + mItems.add(new LogoutAction()); + mHasLogoutButton = true; + } } else { Log.e(TAG, "Invalid global action key " + actionKey); } @@ -351,6 +393,19 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn return dialog; } + private boolean shouldDisplayLockdown() { + int userId = getCurrentUser().id; + // Lockdown is meaningless without a place to go. + if (!mKeyguardManager.isDeviceSecure(userId)) { + return false; + } + + // Only show the lockdown button if the device isn't locked down (for whatever reason). + int state = mLockPatternUtils.getStrongAuthForUser(userId); + return (state == STRONG_AUTH_NOT_REQUIRED + || state == SOME_AUTH_REQUIRED_AFTER_USER_REQUEST); + } + private final class PowerAction extends SinglePressAction implements LongPressAction { private PowerAction() { super(R.drawable.ic_lock_power_off, @@ -416,6 +471,38 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn } + private class ScreenshotAction extends SinglePressAction { + public ScreenshotAction() { + super(R.drawable.ic_screenshot, R.string.global_action_screenshot); + } + + @Override + public void onPress() { + // Add a little delay before executing, to give the + // dialog a chance to go away before it takes a + // screenshot. + // TODO: instead, omit global action dialog layer + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + mScreenshotHelper.takeScreenshot(1, true, true, mHandler); + MetricsLogger.action(mContext, + MetricsEvent.ACTION_SCREENSHOT_POWER_MENU); + } + }, 500); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return false; + } + } + private class BugReportAction extends SinglePressAction implements LongPressAction { public BugReportAction() { @@ -482,6 +569,37 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn } } + private final class LogoutAction extends SinglePressAction { + private LogoutAction() { + super(R.drawable.ic_logout, R.string.global_action_logout); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return false; + } + + @Override + public void onPress() { + // Add a little delay before executing, to give the dialog a chance to go away before + // switching user + mHandler.postDelayed(() -> { + try { + int currentUserId = getCurrentUser().id; + ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM); + ActivityManager.getService().stopUser(currentUserId, true /*force*/, null); + } catch (RemoteException re) { + Log.e(TAG, "Couldn't logout user " + re); + } + }, 500); + } + } + private Action getSettingsAction() { return new SinglePressAction(R.drawable.ic_settings, R.string.global_action_settings) { @@ -575,7 +693,9 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn @Override public void onPress() { - new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL); + new LockPatternUtils(mContext) + .requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, + UserHandle.USER_ALL); try { WindowManagerGlobal.getWindowManagerService().lockNow(null); } catch (RemoteException e) { @@ -754,7 +874,9 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn public View getView(int position, View convertView, ViewGroup parent) { Action action = getItem(position); View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); - if (position == 2) { + // Power off, restart, logout (if present) and lockdown (if present) should be in white + // background. Set the division based on which buttons are currently being displayed. + if (position == 2 + (mHasLogoutButton ? 1 : 0) + (mHasLockdownButton ? 1 : 0)) { HardwareUiLayout.get(parent).setDivisionView(view); } return view; @@ -1247,6 +1369,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn mListView = findViewById(android.R.id.list); mHardwareLayout = HardwareUiLayout.get(mListView); mHardwareLayout.setOutsideTouchListener(view -> dismiss()); + setTitle(R.string.global_actions); } private void updateList() { @@ -1273,7 +1396,23 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn mGradientDrawable.setScreenSize(displaySize.x, displaySize.y); GradientColors colors = mColorExtractor.getColors(mKeyguardShowing ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM); - mGradientDrawable.setColors(colors, false); + updateColors(colors, false /* animate */); + } + + /** + * Updates background and system bars according to current GradientColors. + * @param colors Colors and hints to use. + * @param animate Interpolates gradient if true, just sets otherwise. + */ + private void updateColors(GradientColors colors, boolean animate) { + mGradientDrawable.setColors(colors, animate); + View decorView = getWindow().getDecorView(); + if (colors.supportsDarkText()) { + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR | + View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } else { + decorView.setSystemUiVisibility(0); + } } @Override @@ -1326,28 +1465,16 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn } @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { - for (int i = 0; i < mAdapter.getCount(); ++i) { - CharSequence label = - mAdapter.getItem(i).getLabelForAccessibility(getContext()); - if (label != null) { - event.getText().add(label); - } - } - } - return super.dispatchPopulateAccessibilityEvent(event); - } - - @Override public void onColorsChanged(ColorExtractor extractor, int which) { if (mKeyguardShowing) { if ((WallpaperManager.FLAG_LOCK & which) != 0) { - mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_LOCK)); + updateColors(extractor.getColors(WallpaperManager.FLAG_LOCK), + true /* animate */); } } else { if ((WallpaperManager.FLAG_SYSTEM & which) != 0) { - mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM)); + updateColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM), + true /* animate */); } } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index 2cf230c8e12d..35634374d5dd 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -14,12 +14,12 @@ package com.android.systemui.globalactions; +import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS; + import android.app.Dialog; import android.app.KeyguardManager; -import android.app.WallpaperColors; import android.app.WallpaperManager; import android.content.Context; -import android.graphics.Color; import android.graphics.Point; import android.view.ViewGroup; import android.view.Window; @@ -32,12 +32,14 @@ import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.colorextraction.drawable.GradientDrawable; import com.android.settingslib.Utils; import com.android.systemui.Dependency; +import com.android.systemui.SysUiServiceProvider; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.GlobalActions; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardMonitor; -public class GlobalActionsImpl implements GlobalActions { +public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks { private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f; @@ -45,15 +47,23 @@ public class GlobalActionsImpl implements GlobalActions { private final KeyguardMonitor mKeyguardMonitor; private final DeviceProvisionedController mDeviceProvisionedController; private GlobalActionsDialog mGlobalActions; + private boolean mDisabled; public GlobalActionsImpl(Context context) { mContext = context; mKeyguardMonitor = Dependency.get(KeyguardMonitor.class); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); + SysUiServiceProvider.getComponent(context, CommandQueue.class).addCallbacks(this); + } + + @Override + public void destroy() { + SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallbacks(this); } @Override public void showGlobalActions(GlobalActionsManager manager) { + if (mDisabled) return; if (mGlobalActions == null) { mGlobalActions = new GlobalActionsDialog(mContext, manager); } @@ -107,4 +117,14 @@ public class GlobalActionsImpl implements GlobalActions { d.show(); } + + @Override + public void disable(int state1, int state2, boolean animate) { + final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0; + if (disabled == mDisabled) return; + mDisabled = disabled; + if (disabled && mGlobalActions != null) { + mGlobalActions.dismissDialog(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 2a5ae0d3efc8..22b41a4f9cfa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -96,9 +96,9 @@ public class KeyguardService extends Service { } @Override // Binder interface - public void dismiss(IKeyguardDismissCallback callback) { + public void dismiss(IKeyguardDismissCallback callback, CharSequence message) { checkPermission(); - mKeyguardViewMediator.dismiss(callback); + mKeyguardViewMediator.dismiss(callback, message); } @Override // Binder interface diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java new file mode 100644 index 000000000000..c7d276c1b7a3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -0,0 +1,209 @@ +/* + * 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.keyguard; + +import android.app.ActivityManager; +import android.app.AlarmManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.drawable.Icon; +import android.icu.text.DateFormat; +import android.icu.text.DisplayContext; +import android.net.Uri; +import android.os.Handler; +import android.text.TextUtils; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.R; +import com.android.systemui.statusbar.policy.NextAlarmController; +import com.android.systemui.statusbar.policy.NextAlarmControllerImpl; + +import java.util.Date; +import java.util.Locale; + +import androidx.app.slice.Slice; +import androidx.app.slice.SliceProvider; +import androidx.app.slice.builders.ListBuilder; +import androidx.app.slice.builders.ListBuilder.RowBuilder; + +/** + * Simple Slice provider that shows the current date. + */ +public class KeyguardSliceProvider extends SliceProvider implements + NextAlarmController.NextAlarmChangeCallback { + + public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main"; + public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date"; + public static final String KEYGUARD_NEXT_ALARM_URI = + "content://com.android.systemui.keyguard/alarm"; + + private final Date mCurrentTime = new Date(); + protected final Uri mSliceUri; + protected final Uri mDateUri; + protected final Uri mAlarmUri; + private final Handler mHandler; + private String mDatePattern; + private DateFormat mDateFormat; + private String mLastText; + private boolean mRegistered; + private boolean mRegisteredEveryMinute; + private String mNextAlarm; + private NextAlarmController mNextAlarmController; + + /** + * Receiver responsible for time ticking and updating the date format. + */ + @VisibleForTesting + final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (Intent.ACTION_TIME_TICK.equals(action) + || Intent.ACTION_DATE_CHANGED.equals(action) + || Intent.ACTION_TIME_CHANGED.equals(action) + || Intent.ACTION_TIMEZONE_CHANGED.equals(action) + || Intent.ACTION_LOCALE_CHANGED.equals(action)) { + if (Intent.ACTION_LOCALE_CHANGED.equals(action) + || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) { + // need to get a fresh date format + mHandler.post(KeyguardSliceProvider.this::cleanDateFormat); + } + mHandler.post(KeyguardSliceProvider.this::updateClock); + } + } + }; + + public KeyguardSliceProvider() { + this(new Handler()); + } + + @VisibleForTesting + KeyguardSliceProvider(Handler handler) { + mHandler = handler; + mSliceUri = Uri.parse(KEYGUARD_SLICE_URI); + mDateUri = Uri.parse(KEYGUARD_DATE_URI); + mAlarmUri = Uri.parse(KEYGUARD_NEXT_ALARM_URI); + } + + @Override + public Slice onBindSlice(Uri sliceUri) { + ListBuilder builder = new ListBuilder(getContext(), mSliceUri); + builder.addRow(new RowBuilder(builder, mDateUri).setTitle(mLastText)); + if (!TextUtils.isEmpty(mNextAlarm)) { + Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big); + builder.addRow(new RowBuilder(builder, mAlarmUri) + .setTitle(mNextAlarm).addEndItem(icon)); + } + + return builder.build(); + } + + @Override + public boolean onCreateSliceProvider() { + mNextAlarmController = new NextAlarmControllerImpl(getContext()); + mNextAlarmController.addCallback(this); + mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern); + registerClockUpdate(false /* everyMinute */); + updateClock(); + return true; + } + + public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) { + if (info == null) { + return ""; + } + String skeleton = android.text.format.DateFormat + .is24HourFormat(context, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma"; + String pattern = android.text.format.DateFormat + .getBestDateTimePattern(Locale.getDefault(), skeleton); + return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString(); + } + + /** + * Registers a broadcast receiver for clock updates, include date, time zone and manually + * changing the date/time via the settings app. + * + * @param everyMinute {@code true} if you also want updates every minute. + */ + protected void registerClockUpdate(boolean everyMinute) { + if (mRegistered) { + if (mRegisteredEveryMinute == everyMinute) { + return; + } else { + unregisterClockUpdate(); + } + } + + IntentFilter filter = new IntentFilter(); + if (everyMinute) { + filter.addAction(Intent.ACTION_TIME_TICK); + } + filter.addAction(Intent.ACTION_DATE_CHANGED); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + filter.addAction(Intent.ACTION_LOCALE_CHANGED); + getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/, + null /* scheduler */); + mRegistered = true; + mRegisteredEveryMinute = everyMinute; + } + + protected void unregisterClockUpdate() { + if (!mRegistered) { + return; + } + getContext().unregisterReceiver(mIntentReceiver); + mRegistered = false; + } + + @VisibleForTesting + boolean isRegistered() { + return mRegistered; + } + + protected void updateClock() { + final String text = getFormattedDate(); + if (!text.equals(mLastText)) { + mLastText = text; + getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */); + } + } + + protected String getFormattedDate() { + if (mDateFormat == null) { + final Locale l = Locale.getDefault(); + DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l); + format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE); + mDateFormat = format; + } + mCurrentTime.setTime(System.currentTimeMillis()); + return mDateFormat.format(mCurrentTime); + } + + @VisibleForTesting + void cleanDateFormat() { + mDateFormat = null; + } + + @Override + public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) { + mNextAlarm = formatNextAlarm(getContext(), nextAlarm); + getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 28adca97b8bf..eedc50f23d60 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -25,9 +25,9 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT; -import android.app.Activity; import android.app.ActivityManager; import android.app.AlarmManager; +import android.app.NotificationManager; import android.app.PendingIntent; import android.app.StatusBarManager; import android.app.trust.TrustManager; @@ -52,14 +52,13 @@ import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; -import android.provider.Settings.System; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.view.ViewGroup; -import android.view.WindowManagerPolicy; +import android.view.WindowManagerPolicyConstants; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -76,20 +75,16 @@ import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardSecurityView; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; -import com.android.keyguard.LatencyTracker; +import com.android.internal.util.LatencyTracker; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.Dependency; import com.android.systemui.SystemUI; import com.android.systemui.SystemUIFactory; import com.android.systemui.UiOffloadThread; import com.android.systemui.classifier.FalsingManager; -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.statusbar.phone.FingerprintUnlockController; import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.phone.StatusBarWindowManager; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -128,7 +123,7 @@ import java.util.ArrayList; * false, this will override all other conditions for turning on the keyguard. * * Threading and synchronization: - * This class is created by the initialization routine of the {@link android.view.WindowManagerPolicy}, + * This class is created by the initialization routine of the {@link WindowManagerPolicyConstants}, * and runs on its thread. The keyguard UI is created from that thread in the * constructor of this class. The apis may be called from other threads, including the * {@link com.android.server.input.InputManagerService}'s and {@link android.view.WindowManager}'s. @@ -168,7 +163,6 @@ public class KeyguardViewMediator extends SystemUI { private static final int NOTIFY_SCREEN_TURNED_ON = 15; private static final int NOTIFY_SCREEN_TURNED_OFF = 16; private static final int NOTIFY_STARTED_GOING_TO_SLEEP = 17; - private static final int SET_SWITCHING_USER = 18; /** * The default amount of time we stay awake (used for all key input) @@ -349,6 +343,7 @@ public class KeyguardViewMediator extends SystemUI { private boolean mWakeAndUnlocking; private IKeyguardDrawnCallback mDrawnCallback; private boolean mLockWhenSimRemoved; + private CharSequence mCustomMessage; KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { @@ -373,7 +368,7 @@ public class KeyguardViewMediator extends SystemUI { return; } else if (info.isGuest() || info.isDemo()) { // If we just switched to a guest, try to dismiss keyguard. - dismiss(null /* callback */); + dismiss(null /* callback */, null /* message */); } } } @@ -618,6 +613,13 @@ public class KeyguardViewMediator extends SystemUI { } @Override + public void onBouncerVisiblityChanged(boolean shown) { + synchronized (KeyguardViewMediator.this) { + adjustStatusBarLocked(shown); + } + } + + @Override public void playTrustedSound() { KeyguardViewMediator.this.playTrustedSound(); } @@ -652,6 +654,13 @@ public class KeyguardViewMediator extends SystemUI { } @Override + public CharSequence consumeCustomMessage() { + final CharSequence message = mCustomMessage; + mCustomMessage = null; + return message; + } + + @Override public void onSecondaryDisplayShowingChanged(int displayId) { synchronized (KeyguardViewMediator.this) { setShowingLocked(mShowing, displayId, false); @@ -698,6 +707,9 @@ public class KeyguardViewMediator extends SystemUI { && !mLockPatternUtils.isLockScreenDisabled( KeyguardUpdateMonitor.getCurrentUser()), mSecondaryDisplayShowing, true /* forceCallbacks */); + } else { + // The system's keyguard is disabled or missing. + setShowingLocked(false, mSecondaryDisplayShowing, true); } mStatusBarKeyguardViewManager = @@ -765,8 +777,8 @@ public class KeyguardViewMediator extends SystemUI { /** * Called to let us know the screen was turned off. - * @param why either {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_USER} or - * {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}. + * @param why either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER} or + * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}. */ public void onStartedGoingToSleep(int why) { if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + why + ")"); @@ -796,8 +808,8 @@ public class KeyguardViewMediator extends SystemUI { } } else if (mShowing) { mPendingReset = true; - } else if ((why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT && timeout > 0) - || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) { + } else if ((why == WindowManagerPolicyConstants.OFF_BECAUSE_OF_TIMEOUT && timeout > 0) + || (why == WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER && !lockImmediately)) { doKeyguardLaterLocked(timeout); mLockLater = true; } else if (!mLockPatternUtils.isLockScreenDisabled(currentUser)) { @@ -870,7 +882,7 @@ public class KeyguardViewMediator extends SystemUI { // From DevicePolicyAdmin final long policyTimeout = mLockPatternUtils.getDevicePolicyManager() - .getMaximumTimeToLockForUserAndProfiles(userId); + .getMaximumTimeToLock(null, userId); long timeout; @@ -1030,7 +1042,7 @@ public class KeyguardViewMediator extends SystemUI { } /** - * Same semantics as {@link android.view.WindowManagerPolicy#enableKeyguard}; provide + * Same semantics as {@link WindowManagerPolicyConstants#enableKeyguard}; provide * a way for external stuff to override normal keyguard behavior. For instance * the phone app disables the keyguard when it receives incoming calls. */ @@ -1319,20 +1331,22 @@ public class KeyguardViewMediator extends SystemUI { /** * Dismiss the keyguard through the security layers. * @param callback Callback to be informed about the result + * @param message Message that should be displayed on the bouncer. */ - private void handleDismiss(IKeyguardDismissCallback callback) { + private void handleDismiss(IKeyguardDismissCallback callback, CharSequence message) { if (mShowing) { if (callback != null) { mDismissCallbackRegistry.addCallback(callback); } + mCustomMessage = message; mStatusBarKeyguardViewManager.dismissAndCollapse(); } else if (callback != null) { new DismissCallbackWrapper(callback).notifyDismissError(); } } - public void dismiss(IKeyguardDismissCallback callback) { - mHandler.obtainMessage(DISMISS, callback).sendToTarget(); + public void dismiss(IKeyguardDismissCallback callback, CharSequence message) { + mHandler.obtainMessage(DISMISS, new DismissMessage(callback, message)).sendToTarget(); } /** @@ -1423,11 +1437,7 @@ public class KeyguardViewMediator extends SystemUI { } public void setSwitchingUser(boolean switching) { - Trace.beginSection("KeyguardViewMediator#setSwitchingUser"); - mHandler.removeMessages(SET_SWITCHING_USER); - Message msg = mHandler.obtainMessage(SET_SWITCHING_USER, switching ? 1 : 0, 0); - mHandler.sendMessage(msg); - Trace.endSection(); + KeyguardUpdateMonitor.getInstance(mContext).setSwitchingUser(switching); } /** @@ -1553,7 +1563,8 @@ public class KeyguardViewMediator extends SystemUI { } break; case DISMISS: - handleDismiss((IKeyguardDismissCallback) msg.obj); + final DismissMessage message = (DismissMessage) msg.obj; + handleDismiss(message.getCallback(), message.getMessage()); break; case START_KEYGUARD_EXIT_ANIM: Trace.beginSection("KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM"); @@ -1567,11 +1578,6 @@ public class KeyguardViewMediator extends SystemUI { Log.w(TAG, "Timeout while waiting for activity drawn!"); Trace.endSection(); break; - case SET_SWITCHING_USER: - Trace.beginSection("KeyguardViewMediator#handleMessage SET_SWITCHING_USER"); - KeyguardUpdateMonitor.getInstance(mContext).setSwitchingUser(msg.arg1 != 0); - Trace.endSection(); - break; } } }; @@ -1691,6 +1697,8 @@ public class KeyguardViewMediator extends SystemUI { mUiOffloadThread.submit(() -> { // If the stream is muted, don't play the sound if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return; + // If DND blocks the sound, don't play the sound + if (areSystemSoundsZenModeBlocked(mContext)) return; int id = mLockSounds.play(soundId, mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/); @@ -1702,6 +1710,25 @@ public class KeyguardViewMediator extends SystemUI { } } + private boolean areSystemSoundsZenModeBlocked(Context context) { + int zenMode = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.ZEN_MODE, 0); + + switch (zenMode) { + case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS: + case Settings.Global.ZEN_MODE_ALARMS: + return true; + case Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: + final NotificationManager noMan = (NotificationManager) context + .getSystemService(Context.NOTIFICATION_SERVICE); + return (noMan.getNotificationPolicy().priorityCategories + & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0; + case Settings.Global.ZEN_MODE_OFF: + default: + return false; + } + } + private void playTrustedSound() { playSound(mTrustedSoundId); } @@ -1758,13 +1785,13 @@ public class KeyguardViewMediator extends SystemUI { int flags = 0; if (mStatusBarKeyguardViewManager.shouldDisableWindowAnimationsForUnlock() || mWakeAndUnlocking) { - flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; + flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; } if (mStatusBarKeyguardViewManager.isGoingToNotificationShade()) { - flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; + flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; } if (mStatusBarKeyguardViewManager.isUnlockWithWallpaper()) { - flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; + flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; } mUpdateMonitor.setKeyguardGoingAway(true /* goingAway */); @@ -1855,6 +1882,10 @@ public class KeyguardViewMediator extends SystemUI { } private void adjustStatusBarLocked() { + adjustStatusBarLocked(false /* forceHideHomeRecentsButtons */); + } + + private void adjustStatusBarLocked(boolean forceHideHomeRecentsButtons) { if (mStatusBarManager == null) { mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE); @@ -1865,19 +1896,14 @@ public class KeyguardViewMediator extends SystemUI { // Disable aspects of the system/status/navigation bars that must not be re-enabled by // windows that appear on top, ever int flags = StatusBarManager.DISABLE_NONE; - if (mShowing) { - // Permanently disable components not available when keyguard is enabled - // (like recents). Temporary enable/disable (e.g. the "back" button) are - // done in KeyguardHostView. - flags |= StatusBarManager.DISABLE_RECENT; - } - if (isShowingAndNotOccluded()) { - flags |= StatusBarManager.DISABLE_HOME; + if (forceHideHomeRecentsButtons || isShowingAndNotOccluded()) { + flags |= StatusBarManager.DISABLE_HOME | StatusBarManager.DISABLE_RECENT; } if (DEBUG) { Log.d(TAG, "adjustStatusBarLocked: mShowing=" + mShowing + " mOccluded=" + mOccluded - + " isSecure=" + isSecure() + " --> flags=0x" + Integer.toHexString(flags)); + + " isSecure=" + isSecure() + " force=" + forceHideHomeRecentsButtons + + " --> flags=0x" + Integer.toHexString(flags)); } mStatusBarManager.disable(flags); @@ -2006,12 +2032,9 @@ public class KeyguardViewMediator extends SystemUI { } public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar, - ViewGroup container, - ScrimController scrimController, - FingerprintUnlockController fingerprintUnlockController) { + ViewGroup container, FingerprintUnlockController fingerprintUnlockController) { mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container, - scrimController, fingerprintUnlockController, - mDismissCallbackRegistry); + fingerprintUnlockController, mDismissCallbackRegistry); return mStatusBarKeyguardViewManager; } @@ -2151,4 +2174,22 @@ public class KeyguardViewMediator extends SystemUI { } } } + + private static class DismissMessage { + private final CharSequence mMessage; + private final IKeyguardDismissCallback mCallback; + + DismissMessage(IKeyguardDismissCallback callback, CharSequence message) { + mCallback = callback; + mMessage = message; + } + + public IKeyguardDismissCallback getCallback() { + return mCallback; + } + + public CharSequence getMessage() { + return mMessage; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java index f198229af691..b9e9e0a7f529 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java @@ -16,7 +16,6 @@ package com.android.systemui.keyguard; -import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.IActivityManager; @@ -27,35 +26,42 @@ import android.content.Intent; import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.recents.events.EventBus; +import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; +import com.android.systemui.shared.system.ActivityManagerWrapper; public class WorkLockActivityController { + private static final String TAG = WorkLockActivityController.class.getSimpleName(); + private final Context mContext; - private final SystemServicesProxy mSsp; private final IActivityManager mIam; public WorkLockActivityController(Context context) { - this(context, SystemServicesProxy.getInstance(context), ActivityManager.getService()); + this(context, ActivityManagerWrapper.getInstance(), ActivityManager.getService()); } @VisibleForTesting - WorkLockActivityController(Context context, SystemServicesProxy ssp, IActivityManager am) { + WorkLockActivityController(Context context, ActivityManagerWrapper am, IActivityManager iAm) { mContext = context; - mSsp = ssp; - mIam = am; + mIam = iAm; - mSsp.registerTaskStackListener(mLockListener); + am.registerTaskStackListener(mLockListener); } private void startWorkChallengeInTask(int taskId, int userId) { + ActivityManager.TaskDescription taskDescription = null; + try { + taskDescription = mIam.getTaskDescription(taskId); + } catch (RemoteException e) { + Log.w(TAG, "Failed to get description for task=" + taskId); + } Intent intent = new Intent(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER) .setComponent(new ComponentName(mContext, WorkLockActivity.class)) .putExtra(Intent.EXTRA_USER_ID, userId) - .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, mSsp.getTaskDescription(taskId)) + .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, taskDescription) .addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -69,7 +75,11 @@ public class WorkLockActivityController { } else { // Starting the activity inside the task failed. We can't be sure why, so to be // safe just remove the whole task if it still exists. - mSsp.removeTask(taskId); + try { + mIam.removeTask(taskId); + } catch (RemoteException e) { + Log.w(TAG, "Failed to get description for task=" + taskId); + } } } @@ -98,7 +108,7 @@ public class WorkLockActivityController { } } - private final TaskStackListener mLockListener = new TaskStackListener() { + private final SysUiTaskStackChangeListener mLockListener = new SysUiTaskStackChangeListener() { @Override public void onTaskProfileLocked(int taskId, int userId) { startWorkChallengeInTask(taskId, userId); diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java index b5c0d5386767..f5f06db348ab 100644 --- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java @@ -133,12 +133,19 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener + mNotificationRampTimeMs + "ms"); } try { Thread.sleep(mNotificationRampTimeMs); - player.start(); } catch (InterruptedException e) { Log.e(mTag, "Exception while sleeping to sync notification playback" + " with ducking", e); } - if (DEBUG) { Log.d(mTag, "player.start"); } + try { + player.start(); + if (DEBUG) { Log.d(mTag, "player.start"); } + } catch (Exception e) { + player.release(); + player = null; + // playing the notification didn't work, revert the focus request + abandonAudioFocusAfterError(); + } if (mPlayer != null) { if (DEBUG) { Log.d(mTag, "mPlayer.release"); } mPlayer.release(); @@ -147,6 +154,8 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener } catch (Exception e) { Log.w(mTag, "error loading sound for " + mCmd.uri, e); + // playing the notification didn't work, revert the focus request + abandonAudioFocusAfterError(); } this.notify(); } @@ -154,6 +163,16 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener } }; + private void abandonAudioFocusAfterError() { + synchronized (mQueueAudioFocusLock) { + if (mAudioManagerWithAudioFocus != null) { + if (DEBUG) Log.d(mTag, "abandoning focus after playback error"); + mAudioManagerWithAudioFocus.abandonAudioFocus(null); + mAudioManagerWithAudioFocus = null; + } + } + } + private void startSound(Command cmd) { // Preparing can be slow, so if there is something else // is playing, let it continue until we're done, so there diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java deleted file mode 100644 index abc5667251ea..000000000000 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java +++ /dev/null @@ -1,161 +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 com.android.systemui.pip.phone; - -import static android.view.WindowManager.INPUT_CONSUMER_PIP; - -import android.os.Looper; -import android.os.RemoteException; -import android.util.Log; -import android.view.BatchedInputEventReceiver; -import android.view.Choreographer; -import android.view.InputChannel; -import android.view.InputEvent; -import android.view.IWindowManager; -import android.view.MotionEvent; - -import com.android.systemui.recents.misc.Utilities; - -import java.io.PrintWriter; - -/** - * Manages the input consumer that allows the SystemUI to control the PiP. - */ -public class InputConsumerController { - - private static final String TAG = InputConsumerController.class.getSimpleName(); - - /** - * Listener interface for callers to subscribe to touch events. - */ - public interface TouchListener { - boolean onTouchEvent(MotionEvent ev); - } - - /** - * Listener interface for callers to learn when this class is registered or unregistered with - * window manager - */ - public interface RegistrationListener { - void onRegistrationChanged(boolean isRegistered); - } - - /** - * Input handler used for the PiP input consumer. Input events are batched and consumed with the - * SurfaceFlinger vsync. - */ - private final class PipInputEventReceiver extends BatchedInputEventReceiver { - - public PipInputEventReceiver(InputChannel inputChannel, Looper looper) { - super(inputChannel, looper, Choreographer.getSfInstance()); - } - - @Override - public void onInputEvent(InputEvent event, int displayId) { - boolean handled = true; - try { - // To be implemented for input handling over Pip windows - if (mListener != null && event instanceof MotionEvent) { - MotionEvent ev = (MotionEvent) event; - handled = mListener.onTouchEvent(ev); - } - } finally { - finishInputEvent(event, handled); - } - } - } - - private IWindowManager mWindowManager; - - private PipInputEventReceiver mInputEventReceiver; - private TouchListener mListener; - private RegistrationListener mRegistrationListener; - - public InputConsumerController(IWindowManager windowManager) { - mWindowManager = windowManager; - registerInputConsumer(); - } - - /** - * Sets the touch listener. - */ - public void setTouchListener(TouchListener listener) { - mListener = listener; - } - - /** - * Sets the registration listener. - */ - public void setRegistrationListener(RegistrationListener listener) { - mRegistrationListener = listener; - if (mRegistrationListener != null) { - mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null); - } - } - - /** - * Check if the InputConsumer is currently registered with WindowManager - * - * @return {@code true} if registered, {@code false} if not. - */ - public boolean isRegistered() { - return mInputEventReceiver != null; - } - - /** - * Registers the input consumer. - */ - public void registerInputConsumer() { - if (mInputEventReceiver == null) { - final InputChannel inputChannel = new InputChannel(); - try { - mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP); - mWindowManager.createInputConsumer(INPUT_CONSUMER_PIP, inputChannel); - } catch (RemoteException e) { - Log.e(TAG, "Failed to create PIP input consumer", e); - } - mInputEventReceiver = new PipInputEventReceiver(inputChannel, Looper.myLooper()); - if (mRegistrationListener != null) { - mRegistrationListener.onRegistrationChanged(true /* isRegistered */); - } - } - } - - /** - * Unregisters the input consumer. - */ - public void unregisterInputConsumer() { - if (mInputEventReceiver != null) { - try { - mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP); - } catch (RemoteException e) { - Log.e(TAG, "Failed to destroy PIP input consumer", e); - } - mInputEventReceiver.dispose(); - mInputEventReceiver = null; - if (mRegistrationListener != null) { - mRegistrationListener.onRegistrationChanged(false /* isRegistered */); - } - } - } - - public void dump(PrintWriter pw, String prefix) { - final String innerPrefix = prefix + " "; - pw.println(prefix + TAG); - pw.println(innerPrefix + "registered=" + (mInputEventReceiver != null)); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java new file mode 100644 index 000000000000..f0e4ccc139ca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java @@ -0,0 +1,89 @@ +/* + * 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.pip.phone; + +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE; + +import android.app.AppOpsManager; +import android.app.AppOpsManager.OnOpChangedListener; +import android.app.IActivityManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.util.Pair; + +public class PipAppOpsListener { + private static final String TAG = PipAppOpsListener.class.getSimpleName(); + + private Context mContext; + private IActivityManager mActivityManager; + private AppOpsManager mAppOpsManager; + + private PipMotionHelper mMotionHelper; + + private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() { + @Override + public void onOpChanged(String op, String packageName) { + try { + // Dismiss the PiP once the user disables the app ops setting for that package + final Pair<ComponentName, Integer> topPipActivityInfo = + PipUtils.getTopPinnedActivity(mContext, mActivityManager); + if (topPipActivityInfo.first != null) { + final ApplicationInfo appInfo = mContext.getPackageManager() + .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second); + if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) && + mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid, + packageName) != MODE_ALLOWED) { + mMotionHelper.dismissPip(); + } + } + } catch (NameNotFoundException e) { + // Unregister the listener if the package can't be found + unregisterAppOpsListener(); + } + } + }; + + public PipAppOpsListener(Context context, IActivityManager activityManager, + PipMotionHelper motionHelper) { + mContext = context; + mActivityManager = activityManager; + mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + mMotionHelper = motionHelper; + } + + public void onActivityPinned(String packageName) { + // Register for changes to the app ops setting for this package while it is in PiP + registerAppOpsListener(packageName); + } + + public void onActivityUnpinned() { + // Unregister for changes to the previously PiP'ed package + unregisterAppOpsListener(); + } + + private void registerAppOpsListener(String packageName) { + mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName, + mAppOpsChangedListener); + } + + private void unregisterAppOpsListener() { + mAppOpsManager.stopWatchingMode(mAppOpsChangedListener); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index bae9ef8abfba..24d0126a1494 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -16,11 +16,10 @@ package com.android.systemui.pip.phone; -import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.INPUT_CONSUMER_PIP; import android.app.ActivityManager; -import android.app.ActivityManager.StackInfo; import android.app.IActivityManager; import android.content.ComponentName; import android.content.Context; @@ -39,9 +38,10 @@ import android.view.WindowManagerGlobal; import com.android.systemui.pip.BasePipManager; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.component.ExpandPipEvent; +import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; -import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.InputConsumerController; import java.io.PrintWriter; @@ -63,20 +63,19 @@ public class PipManager implements BasePipManager { private InputConsumerController mInputConsumerController; private PipMenuActivityController mMenuController; private PipMediaController mMediaController; - private PipNotificationController mNotificationController; private PipTouchHandler mTouchHandler; + private PipAppOpsListener mAppOpsListener; /** * Handler for system task stack changes. */ - TaskStackListener mTaskStackListener = new TaskStackListener() { + SysUiTaskStackChangeListener mTaskStackListener = new SysUiTaskStackChangeListener() { @Override - public void onActivityPinned(String packageName, int userId, int taskId) { + public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { mTouchHandler.onActivityPinned(); mMediaController.onActivityPinned(); mMenuController.onActivityPinned(); - mNotificationController.onActivityPinned(packageName, userId, - true /* deferUntilAnimationEnds */); + mAppOpsListener.onActivityPinned(packageName); SystemServicesProxy.getInstance(mContext).setPipVisibility(true); } @@ -89,7 +88,7 @@ public class PipManager implements BasePipManager { final int userId = topActivity != null ? topPipActivityInfo.second : 0; mMenuController.onActivityUnpinned(); mTouchHandler.onActivityUnpinned(topActivity); - mNotificationController.onActivityUnpinned(topActivity, userId); + mAppOpsListener.onActivityUnpinned(); SystemServicesProxy.getInstance(mContext).setPipVisibility(topActivity != null); } @@ -106,7 +105,6 @@ public class PipManager implements BasePipManager { mTouchHandler.setTouchEnabled(true); mTouchHandler.onPinnedStackAnimationEnded(); mMenuController.onPinnedStackAnimationEnded(); - mNotificationController.onPinnedStackAnimationEnded(); } @Override @@ -173,15 +171,16 @@ public class PipManager implements BasePipManager { } catch (RemoteException e) { Log.e(TAG, "Failed to register pinned stack listener", e); } - SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener); + ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); - mInputConsumerController = new InputConsumerController(mWindowManager); + mInputConsumerController = InputConsumerController.getPipInputConsumer(); + mInputConsumerController.registerInputConsumer(); mMediaController = new PipMediaController(context, mActivityManager); mMenuController = new PipMenuActivityController(context, mActivityManager, mMediaController, mInputConsumerController); mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController, mInputConsumerController); - mNotificationController = new PipNotificationController(context, mActivityManager, + mAppOpsListener = new PipAppOpsListener(context, mActivityManager, mTouchHandler.getMotionHelper()); EventBus.getDefault().register(this); } @@ -197,19 +196,6 @@ public class PipManager implements BasePipManager { * Expands the PIP. */ public final void onBusEvent(ExpandPipEvent event) { - if (event.clearThumbnailWindows) { - try { - StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID); - if (stackInfo != null && stackInfo.taskIds != null) { - SystemServicesProxy ssp = SystemServicesProxy.getInstance(mContext); - for (int taskId : stackInfo.taskIds) { - ssp.cancelThumbnailTransition(taskId); - } - } - } catch (RemoteException e) { - // Do nothing - } - } mTouchHandler.getMotionHelper().expandPip(false /* skipAnimation */); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index 90f7b8db1c59..0486a9dcca74 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -16,6 +16,10 @@ package com.android.systemui.pip.phone; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS; + import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS; import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT; import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER; @@ -39,6 +43,7 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.PendingIntent.CanceledException; import android.app.RemoteAction; +import android.content.ComponentName; import android.content.Intent; import android.content.pm.ParceledListSlice; import android.graphics.Color; @@ -46,12 +51,15 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; +import android.util.Pair; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -105,6 +113,7 @@ public class PipMenuActivity extends Activity { private Drawable mBackgroundDrawable; private View mMenuContainer; private LinearLayout mActionsGroup; + private View mSettingsButton; private View mDismissButton; private ImageView mExpandButton; private int mBetweenActionPaddingLand; @@ -218,6 +227,11 @@ public class PipMenuActivity extends Activity { } return true; }); + mSettingsButton = findViewById(R.id.settings); + mSettingsButton.setAlpha(0); + mSettingsButton.setOnClickListener((v) -> { + showSettings(); + }); mDismissButton = findViewById(R.id.dismiss); mDismissButton.setAlpha(0); mDismissButton.setOnClickListener((v) -> { @@ -352,12 +366,14 @@ public class PipMenuActivity extends Activity { ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, mMenuContainer.getAlpha(), 1f); menuAnim.addUpdateListener(mMenuBgUpdateListener); + ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, + mSettingsButton.getAlpha(), 1f); ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, mDismissButton.getAlpha(), 1f); if (menuState == MENU_STATE_FULL) { - mMenuContainerAnimator.playTogether(menuAnim, dismissAnim); + mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim); } else { - mMenuContainerAnimator.play(dismissAnim); + mMenuContainerAnimator.playTogether(dismissAnim); } mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN); mMenuContainerAnimator.setDuration(MENU_FADE_DURATION); @@ -394,9 +410,11 @@ public class PipMenuActivity extends Activity { ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, mMenuContainer.getAlpha(), 0f); menuAnim.addUpdateListener(mMenuBgUpdateListener); + ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, + mSettingsButton.getAlpha(), 0f); ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, mDismissButton.getAlpha(), 0f); - mMenuContainerAnimator.playTogether(menuAnim, dismissAnim); + mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim); mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT); mMenuContainerAnimator.setDuration(MENU_FADE_DURATION); mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { @@ -526,12 +544,14 @@ public class PipMenuActivity extends Activity { final float menuAlpha = 1 - fraction; if (mMenuState == MENU_STATE_FULL) { mMenuContainer.setAlpha(menuAlpha); + mSettingsButton.setAlpha(menuAlpha); mDismissButton.setAlpha(menuAlpha); final float interpolatedAlpha = MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction; alpha = (int) (interpolatedAlpha * 255); } else { if (mMenuState == MENU_STATE_CLOSE) { + mSettingsButton.setAlpha(menuAlpha); mDismissButton.setAlpha(menuAlpha); } alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255); @@ -588,6 +608,19 @@ public class PipMenuActivity extends Activity { sendMessage(m, "Could not notify controller to show PIP menu"); } + private void showSettings() { + final Pair<ComponentName, Integer> topPipActivityInfo = + PipUtils.getTopPinnedActivity(this, ActivityManager.getService()); + if (topPipActivityInfo.first != null) { + final UserHandle user = UserHandle.of(topPipActivityInfo.second); + final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS, + Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null)); + settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user); + settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); + startActivity(settingsIntent); + } + } + private void notifyActivityCallback(Messenger callback) { Message m = Message.obtain(); m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java index 68743b34884d..26fced307bac 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -16,13 +16,13 @@ package com.android.systemui.pip.phone; -import static android.app.ActivityManager.StackId.PINNED_STACK_ID; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import android.app.ActivityManager.StackInfo; import android.app.ActivityOptions; import android.app.IActivityManager; import android.app.RemoteAction; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; @@ -42,6 +42,7 @@ import com.android.systemui.pip.phone.PipMediaController.ActionListener; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.component.HidePipMenuEvent; import com.android.systemui.recents.misc.ReferenceCountedTrigger; +import com.android.systemui.shared.system.InputConsumerController; import java.io.PrintWriter; import java.util.ArrayList; @@ -383,7 +384,8 @@ public class PipMenuActivityController { private void startMenuActivity(int menuState, Rect stackBounds, Rect movementBounds, boolean allowMenuTimeout, boolean willResizeMenu) { try { - StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID); + StackInfo pinnedStackInfo = mActivityManager.getStackInfo( + WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null && pinnedStackInfo.taskIds.length > 0) { Intent intent = new Intent(mContext, PipMenuActivity.class); @@ -421,7 +423,8 @@ public class PipMenuActivityController { // Fetch the pinned stack bounds Rect stackBounds = null; try { - StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID); + StackInfo pinnedStackInfo = mActivityManager.getStackInfo( + WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); if (pinnedStackInfo != null) { stackBounds = pinnedStackInfo.bounds; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index cebb22f07aaf..21a836c030cb 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -16,8 +16,10 @@ package com.android.systemui.pip.phone; -import static android.app.ActivityManager.StackId.PINNED_STACK_ID; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN; import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN; import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN; @@ -121,7 +123,8 @@ public class PipMotionHelper implements Handler.Callback { void synchronizePinnedStackBounds() { cancelAnimations(); try { - StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID); + StackInfo stackInfo = + mActivityManager.getStackInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); if (stackInfo != null) { mBounds.set(stackInfo.bounds); } @@ -158,13 +161,7 @@ public class PipMotionHelper implements Handler.Callback { mMenuController.hideMenuWithoutResize(); mHandler.post(() -> { try { - if (skipAnimation) { - mActivityManager.moveTasksToFullscreenStack(PINNED_STACK_ID, true /* onTop */); - } else { - mActivityManager.resizeStack(PINNED_STACK_ID, null /* bounds */, - true /* allowResizeInDockedMode */, true /* preserveWindows */, - true /* animate */, EXPAND_STACK_TO_FULLSCREEN_DURATION); - } + mActivityManager.dismissPip(!skipAnimation, EXPAND_STACK_TO_FULLSCREEN_DURATION); } catch (RemoteException e) { Log.e(TAG, "Error expanding PiP activity", e); } @@ -182,7 +179,7 @@ public class PipMotionHelper implements Handler.Callback { mMenuController.hideMenuWithoutResize(); mHandler.post(() -> { try { - mActivityManager.removeStack(PINNED_STACK_ID); + mActivityManager.removeStacksInWindowingModes(new int[]{ WINDOWING_MODE_PINNED }); } catch (RemoteException e) { Log.e(TAG, "Failed to remove PiP", e); } @@ -529,14 +526,15 @@ public class PipMotionHelper implements Handler.Callback { Rect toBounds = (Rect) args.arg1; int duration = args.argi1; try { - StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID); + StackInfo stackInfo = mActivityManager.getStackInfo( + WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); if (stackInfo == null) { // In the case where we've already re-expanded or dismissed the PiP, then // just skip the resize return true; } - mActivityManager.resizeStack(PINNED_STACK_ID, toBounds, + mActivityManager.resizeStack(stackInfo.stackId, toBounds, false /* allowResizeInDockedMode */, true /* preserveWindows */, true /* animate */, duration); mBounds.set(toBounds); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java deleted file mode 100644 index 6d083e9d601d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java +++ /dev/null @@ -1,231 +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 com.android.systemui.pip.phone; - -import static android.app.AppOpsManager.MODE_ALLOWED; -import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE; -import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; -import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS; - -import android.app.AppOpsManager; -import android.app.AppOpsManager.OnOpChangedListener; -import android.app.IActivityManager; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.UserHandle; -import android.util.IconDrawableFactory; -import android.util.Log; -import android.util.Pair; - -import com.android.systemui.R; -import com.android.systemui.SystemUI; -import com.android.systemui.util.NotificationChannels; - -/** - * Manages the BTW notification that shows whenever an activity enters or leaves picture-in-picture. - */ -public class PipNotificationController { - private static final String TAG = PipNotificationController.class.getSimpleName(); - - private static final String NOTIFICATION_TAG = PipNotificationController.class.getName(); - private static final int NOTIFICATION_ID = 0; - - private Context mContext; - private IActivityManager mActivityManager; - private AppOpsManager mAppOpsManager; - private NotificationManager mNotificationManager; - private IconDrawableFactory mIconDrawableFactory; - - private PipMotionHelper mMotionHelper; - - // Used when building a deferred notification - private String mDeferredNotificationPackageName; - private int mDeferredNotificationUserId; - - private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() { - @Override - public void onOpChanged(String op, String packageName) { - try { - // Dismiss the PiP once the user disables the app ops setting for that package - final Pair<ComponentName, Integer> topPipActivityInfo = - PipUtils.getTopPinnedActivity(mContext, mActivityManager); - if (topPipActivityInfo.first != null) { - final ApplicationInfo appInfo = mContext.getPackageManager() - .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second); - if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) && - mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid, - packageName) != MODE_ALLOWED) { - mMotionHelper.dismissPip(); - } - } - } catch (NameNotFoundException e) { - // Unregister the listener if the package can't be found - unregisterAppOpsListener(); - } - } - }; - - public PipNotificationController(Context context, IActivityManager activityManager, - PipMotionHelper motionHelper) { - mContext = context; - mActivityManager = activityManager; - mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); - mNotificationManager = NotificationManager.from(context); - mMotionHelper = motionHelper; - mIconDrawableFactory = IconDrawableFactory.newInstance(context); - } - - public void onActivityPinned(String packageName, int userId, boolean deferUntilAnimationEnds) { - // Clear any existing notification - mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID); - - if (deferUntilAnimationEnds) { - mDeferredNotificationPackageName = packageName; - mDeferredNotificationUserId = userId; - } else { - showNotificationForApp(packageName, userId); - } - - // Register for changes to the app ops setting for this package while it is in PiP - registerAppOpsListener(packageName); - } - - public void onPinnedStackAnimationEnded() { - if (mDeferredNotificationPackageName != null) { - showNotificationForApp(mDeferredNotificationPackageName, mDeferredNotificationUserId); - mDeferredNotificationPackageName = null; - mDeferredNotificationUserId = 0; - } - } - - public void onActivityUnpinned(ComponentName topPipActivity, int userId) { - // Unregister for changes to the previously PiP'ed package - unregisterAppOpsListener(); - - // Reset the deferred notification package - mDeferredNotificationPackageName = null; - mDeferredNotificationUserId = 0; - - if (topPipActivity != null) { - // onActivityUnpinned() is only called after the transition is complete, so we don't - // need to defer until the animation ends to update the notification - onActivityPinned(topPipActivity.getPackageName(), userId, - false /* deferUntilAnimationEnds */); - } else { - mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID); - } - } - - /** - * Builds and shows the notification for the given app. - */ - private void showNotificationForApp(String packageName, int userId) { - // Build a new notification - try { - final UserHandle user = UserHandle.of(userId); - final Context userContext = mContext.createPackageContextAsUser( - mContext.getPackageName(), 0, user); - final Notification.Builder builder = - new Notification.Builder(userContext, NotificationChannels.GENERAL) - .setLocalOnly(true) - .setOngoing(true) - .setSmallIcon(R.drawable.pip_notification_icon) - .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)); - if (updateNotificationForApp(builder, packageName, user)) { - SystemUI.overrideNotificationAppName(mContext, builder); - - // Show the new notification - mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build()); - } - } catch (NameNotFoundException e) { - Log.e(TAG, "Could not show notification for application", e); - } - } - - /** - * Updates the notification builder with app-specific information, returning whether it was - * successful. - */ - private boolean updateNotificationForApp(Notification.Builder builder, String packageName, - UserHandle user) throws NameNotFoundException { - final PackageManager pm = mContext.getPackageManager(); - final ApplicationInfo appInfo; - try { - appInfo = pm.getApplicationInfoAsUser(packageName, 0, user.getIdentifier()); - } catch (NameNotFoundException e) { - Log.e(TAG, "Could not update notification for application", e); - return false; - } - - if (appInfo != null) { - final String appName = pm.getUserBadgedLabel(pm.getApplicationLabel(appInfo), user) - .toString(); - final String message = mContext.getString(R.string.pip_notification_message, appName); - final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS, - Uri.fromParts("package", packageName, null)); - settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user); - settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); - - final Drawable iconDrawable = mIconDrawableFactory.getBadgedIcon(appInfo); - builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName)) - .setContentText(message) - .setContentIntent(PendingIntent.getActivityAsUser(mContext, packageName.hashCode(), - settingsIntent, FLAG_CANCEL_CURRENT, null, user)) - .setStyle(new Notification.BigTextStyle().bigText(message)) - .setLargeIcon(createBitmap(iconDrawable).createAshmemBitmap()); - return true; - } - return false; - } - - private void registerAppOpsListener(String packageName) { - mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName, - mAppOpsChangedListener); - } - - private void unregisterAppOpsListener() { - mAppOpsManager.stopWatchingMode(mAppOpsChangedListener); - } - - /** - * Bakes a drawable into a bitmap. - */ - private Bitmap createBitmap(Drawable d) { - Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), - Config.ARGB_8888); - Canvas c = new Canvas(bitmap); - d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); - d.draw(c); - c.setBitmap(null); - return bitmap; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 2b48e0fb32bd..b25351731a35 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -42,11 +42,11 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; - -import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.internal.policy.PipSnapAlgorithm; import com.android.systemui.R; +import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.statusbar.FlingAnimationUtils; import java.io.PrintWriter; @@ -63,10 +63,6 @@ public class PipTouchHandler { // Allow the PIP to be flung from anywhere on the screen to the bottom to be dismissed. private static final boolean ENABLE_FLING_DISMISS = false; - // These values are used for metrics and should never change - private static final int METRIC_VALUE_DISMISSED_BY_TAP = 0; - private static final int METRIC_VALUE_DISMISSED_BY_DRAG = 1; - private static final int SHOW_DISMISS_AFFORDANCE_DELAY = 225; // Allow dragging the PIP to a location to close it @@ -163,8 +159,7 @@ public class PipTouchHandler { @Override public void onPipDismiss() { mMotionHelper.dismissPip(); - MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED, - METRIC_VALUE_DISMISSED_BY_TAP); + MetricsLoggerWrapper.logPictureInPictureDismissByTap(mContext); } @Override @@ -463,8 +458,7 @@ public class PipTouchHandler { return; } if (mIsMinimized != isMinimized) { - MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MINIMIZED, - isMinimized); + MetricsLoggerWrapper.logPictureInPictureMinimize(mContext, isMinimized); } mIsMinimized = isMinimized; mSnapAlgorithm.setMinimized(isMinimized); @@ -537,8 +531,7 @@ public class PipTouchHandler { mMenuState = menuState; updateMovementBounds(menuState); if (menuState != MENU_STATE_CLOSE) { - MetricsLogger.visibility(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MENU, - menuState == MENU_STATE_FULL); + MetricsLoggerWrapper.logPictureInPictureMenuVisible(mContext, menuState == MENU_STATE_FULL); } } @@ -670,9 +663,7 @@ public class PipTouchHandler { if (mMotionHelper.shouldDismissPip() || isFlingToBot) { mMotionHelper.animateDismiss(mMotionHelper.getBounds(), vel.x, vel.y, mUpdateScrimListener); - MetricsLogger.action(mContext, - MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED, - METRIC_VALUE_DISMISSED_BY_DRAG); + MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext); return true; } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java index 56275fd043cf..2f53de96db2d 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java @@ -16,7 +16,8 @@ package com.android.systemui.pip.phone; -import static android.app.ActivityManager.StackId.PINNED_STACK_ID; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import android.app.ActivityManager.StackInfo; import android.app.IActivityManager; @@ -38,7 +39,8 @@ public class PipUtils { IActivityManager activityManager) { try { final String sysUiPackageName = context.getPackageName(); - final StackInfo pinnedStackInfo = activityManager.getStackInfo(PINNED_STACK_ID); + final StackInfo pinnedStackInfo = + activityManager.getStackInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null && pinnedStackInfo.taskIds.length > 0) { for (int i = pinnedStackInfo.taskNames.length - 1; i >= 0; i--) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java index 2d2869d8817b..a98468017f17 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java @@ -20,7 +20,6 @@ import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManager.StackInfo; import android.app.IActivityManager; -import android.app.RemoteAction; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -46,14 +45,17 @@ import android.view.WindowManagerGlobal; import com.android.systemui.R; import com.android.systemui.pip.BasePipManager; +import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; +import com.android.systemui.shared.system.ActivityManagerWrapper; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; -import static android.app.ActivityManager.StackId.PINNED_STACK_ID; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.view.Display.DEFAULT_DISPLAY; /** @@ -121,6 +123,7 @@ public class PipManager implements BasePipManager { private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED; private boolean mInitialized; private int mPipTaskId = TASK_ID_NO_PIP; + private int mPinnedStackId = INVALID_STACK_ID; private ComponentName mPipComponentName; private MediaController mPipMediaController; private String[] mLastPackagesResourceGranted; @@ -232,7 +235,7 @@ public class PipManager implements BasePipManager { mActivityManager = ActivityManager.getService(); mWindowManager = WindowManagerGlobal.getWindowManagerService(); - SystemServicesProxy.getInstance(context).registerTaskStackListener(mTaskStackListener); + ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED); mContext.registerReceiver(mBroadcastReceiver, intentFilter); @@ -336,9 +339,11 @@ public class PipManager implements BasePipManager { mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener); if (removePipStack) { try { - mActivityManager.removeStack(PINNED_STACK_ID); + mActivityManager.removeStack(mPinnedStackId); } catch (RemoteException e) { Log.e(TAG, "removeStack failed", e); + } finally { + mPinnedStackId = INVALID_STACK_ID; } } for (int i = mListeners.size() - 1; i >= 0; --i) { @@ -424,7 +429,7 @@ public class PipManager implements BasePipManager { } try { int animationDurationMs = -1; - mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds, + mActivityManager.resizeStack(mPinnedStackId, mCurrentPipBounds, true, true, true, animationDurationMs); } catch (RemoteException e) { Log.e(TAG, "resizeStack failed", e); @@ -502,7 +507,8 @@ public class PipManager implements BasePipManager { private StackInfo getPinnedStackInfo() { StackInfo stackInfo = null; try { - stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID); + stackInfo = mActivityManager.getStackInfo( + WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); } catch (RemoteException e) { Log.e(TAG, "getStackInfo failed", e); } @@ -594,8 +600,8 @@ public class PipManager implements BasePipManager { private boolean isSettingsShown() { List<RunningTaskInfo> runningTasks; try { - runningTasks = mActivityManager.getTasks(1, 0); - if (runningTasks == null || runningTasks.size() == 0) { + runningTasks = mActivityManager.getTasks(1); + if (runningTasks.isEmpty()) { return false; } } catch (RemoteException e) { @@ -615,7 +621,7 @@ public class PipManager implements BasePipManager { return false; } - private TaskStackListener mTaskStackListener = new TaskStackListener() { + private SysUiTaskStackChangeListener mTaskStackListener = new SysUiTaskStackChangeListener() { @Override public void onTaskStackChanged() { if (DEBUG) Log.d(TAG, "onTaskStackChanged()"); @@ -652,7 +658,7 @@ public class PipManager implements BasePipManager { } @Override - public void onActivityPinned(String packageName, int userId, int taskId) { + public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { if (DEBUG) Log.d(TAG, "onActivityPinned()"); StackInfo stackInfo = getPinnedStackInfo(); @@ -661,6 +667,7 @@ public class PipManager implements BasePipManager { return; } if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo); + mPinnedStackId = stackInfo.stackId; mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1]; mPipComponentName = ComponentName.unflattenFromString( stackInfo.taskNames[stackInfo.taskNames.length - 1]); diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java new file mode 100644 index 000000000000..bd130f4b40f3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java @@ -0,0 +1,26 @@ +package com.android.systemui.power; + +public interface EnhancedEstimates { + + /** + * Returns a boolean indicating if the hybrid notification should be used. + */ + boolean isHybridNotificationEnabled(); + + /** + * Returns an estimate object if the feature is enabled. + */ + Estimate getEstimate(); + + /** + * Returns a long indicating the amount of time remaining in milliseconds under which we will + * show a regular warning to the user. + */ + long getLowWarningThreshold(); + + /** + * Returns a long indicating the amount of time remaining in milliseconds under which we will + * show a severe warning to the user. + */ + long getSevereWarningThreshold(); +} diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java new file mode 100644 index 000000000000..5686d801bca2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java @@ -0,0 +1,26 @@ +package com.android.systemui.power; + +import android.util.Log; + +public class EnhancedEstimatesImpl implements EnhancedEstimates { + + @Override + public boolean isHybridNotificationEnabled() { + return false; + } + + @Override + public Estimate getEstimate() { + return null; + } + + @Override + public long getLowWarningThreshold() { + return 0; + } + + @Override + public long getSevereWarningThreshold() { + return 0; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/power/Estimate.java b/packages/SystemUI/src/com/android/systemui/power/Estimate.java new file mode 100644 index 000000000000..12a8f0a435b4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/power/Estimate.java @@ -0,0 +1,11 @@ +package com.android.systemui.power; + +public class Estimate { + public final long estimateMillis; + public final boolean isBasedOnUsage; + + public Estimate(long estimateMillis, boolean isBasedOnUsage) { + this.estimateMillis = estimateMillis; + this.isBasedOnUsage = isBasedOnUsage; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index c29b362bda13..3a2b12f4da23 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -17,11 +17,9 @@ package com.android.systemui.power; import android.app.Notification; -import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; @@ -29,23 +27,19 @@ import android.content.DialogInterface.OnDismissListener; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioAttributes; -import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; -import android.os.SystemClock; import android.os.UserHandle; -import android.provider.Settings; import android.support.annotation.VisibleForTesting; import android.util.Slog; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.internal.notification.SystemNotificationChannels; import com.android.settingslib.Utils; +import com.android.settingslib.utils.PowerUtil; import com.android.systemui.R; import com.android.systemui.SystemUI; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.util.NotificationChannels; @@ -96,8 +90,11 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { private long mScreenOffTime; private int mShowing; - private long mBucketDroppedNegativeTimeMs; + private long mWarningTriggerTimeMs; + private Estimate mEstimate; + private long mLowWarningThreshold; + private long mSevereWarningThreshold; private boolean mWarning; private boolean mPlaySound; private boolean mInvalidCharger; @@ -130,14 +127,29 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { public void update(int batteryLevel, int bucket, long screenOffTime) { mBatteryLevel = batteryLevel; if (bucket >= 0) { - mBucketDroppedNegativeTimeMs = 0; + mWarningTriggerTimeMs = 0; } else if (bucket < mBucket) { - mBucketDroppedNegativeTimeMs = System.currentTimeMillis(); + mWarningTriggerTimeMs = System.currentTimeMillis(); } mBucket = bucket; mScreenOffTime = screenOffTime; } + @Override + public void updateEstimate(Estimate estimate) { + mEstimate = estimate; + if (estimate.estimateMillis <= mLowWarningThreshold) { + mWarningTriggerTimeMs = System.currentTimeMillis(); + } + } + + @Override + public void updateThresholds(long lowThreshold, long severeThreshold) { + mLowWarningThreshold = lowThreshold; + mSevereWarningThreshold = severeThreshold; + } + + private void updateNotification() { if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning + " mPlaySound=" + mPlaySound + " mInvalidCharger=" + mInvalidCharger); @@ -171,25 +183,40 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_BAD_CHARGER, n, UserHandle.ALL); } - private void showWarningNotification() { - final int textRes = R.string.battery_low_percent_format; - final String percentage = NumberFormat.getPercentInstance().format((double) mBatteryLevel / 100.0); + protected void showWarningNotification() { + final String percentage = NumberFormat.getPercentInstance() + .format((double) mBatteryLevel / 100.0); + + // get standard notification copy + String title = mContext.getString(R.string.battery_low_title); + String contentText = mContext.getString(R.string.battery_low_percent_format, percentage); + + // override notification copy if hybrid notification enabled + if (mEstimate != null) { + title = mContext.getString(R.string.battery_low_title_hybrid); + contentText = getHybridContentString(percentage); + } final Notification.Builder nb = new Notification.Builder(mContext, NotificationChannels.BATTERY) .setSmallIcon(R.drawable.ic_power_low) // Bump the notification when the bucket dropped. - .setWhen(mBucketDroppedNegativeTimeMs) + .setWhen(mWarningTriggerTimeMs) .setShowWhen(false) - .setContentTitle(mContext.getString(R.string.battery_low_title)) - .setContentText(mContext.getString(textRes, percentage)) + .setContentTitle(title) + .setContentText(contentText) .setOnlyAlertOnce(true) .setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_WARNING)) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .setColor(Utils.getColorAttr(mContext, android.R.attr.colorError)); + .setVisibility(Notification.VISIBILITY_PUBLIC); if (hasBatterySettings()) { nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS)); } + // Make the notification red if the percentage goes below a certain amount or the time + // remaining estimate is disabled + if (mEstimate == null || mBucket < 0 + || mEstimate.estimateMillis < mSevereWarningThreshold) { + nb.setColor(Utils.getColorAttr(mContext, android.R.attr.colorError)); + } nb.addAction(0, mContext.getString(R.string.battery_saver_start_action), pendingBroadcast(ACTION_START_SAVER)); @@ -201,6 +228,14 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, n, UserHandle.ALL); } + private String getHybridContentString(String percentage) { + return PowerUtil.getBatteryRemainingStringFormatted( + mContext, + mEstimate.estimateMillis, + percentage, + mEstimate.isBasedOnUsage); + } + private PendingIntent pendingBroadcast(String action) { return PendingIntent.getBroadcastAsUser(mContext, 0, new Intent(action), 0, UserHandle.CURRENT); diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index f37826853fe5..ac86c8ae097d 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -28,8 +28,14 @@ import android.database.ContentObserver; import android.os.BatteryManager; import android.os.Handler; import android.os.HardwarePropertiesManager; +import android.os.IBinder; +import android.os.IThermalEventListener; +import android.os.IThermalService; import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; +import android.os.Temperature; import android.os.UserHandle; import android.provider.Settings; import android.text.format.DateUtils; @@ -38,6 +44,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; +import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SystemUI; @@ -53,6 +60,7 @@ public class PowerUI extends SystemUI { private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS; private static final long TEMPERATURE_LOGGING_INTERVAL = DateUtils.HOUR_IN_MILLIS; private static final int MAX_RECENT_TEMPS = 125; // TEMPERATURE_LOGGING_INTERVAL plus a buffer + static final long THREE_HOURS_IN_MILLIS = DateUtils.HOUR_IN_MILLIS * 3; private final Handler mHandler = new Handler(); private final Receiver mReceiver = new Receiver(); @@ -62,9 +70,12 @@ public class PowerUI extends SystemUI { private WarningsUI mWarnings; private final Configuration mLastConfiguration = new Configuration(); private int mBatteryLevel = 100; - private int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN; + private long mTimeRemaining = Long.MAX_VALUE; private int mPlugType = 0; private int mInvalidCharger = 0; + private EnhancedEstimates mEnhancedEstimates; + private boolean mLowWarningShownThisChargeCycle; + private boolean mSevereWarningShownThisChargeCycle; private int mLowBatteryAlertCloseLevel; private final int[] mLowBatteryReminderLevels = new int[2]; @@ -75,9 +86,12 @@ public class PowerUI extends SystemUI { private float[] mRecentTemps = new float[MAX_RECENT_TEMPS]; private int mNumTemps; private long mNextLogTime; + private IThermalService mThermalService; + + @VisibleForTesting int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN; - // We create a method reference here so that we are guaranteed that we can remove a callback // by using the same instance (method references are not guaranteed to be the same object + // We create a method reference here so that we are guaranteed that we can remove a callback // each time they are created). private final Runnable mUpdateTempCallback = this::updateTemperatureWarning; @@ -87,6 +101,7 @@ public class PowerUI extends SystemUI { mContext.getSystemService(Context.HARDWARE_PROPERTIES_SERVICE); mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime(); mWarnings = Dependency.get(WarningsUI.class); + mEnhancedEstimates = Dependency.get(EnhancedEstimates.class); mLastConfiguration.setTo(mContext.getResources().getConfiguration()); ContentObserver obs = new ContentObserver(mHandler) { @@ -124,10 +139,19 @@ public class PowerUI extends SystemUI { com.android.internal.R.integer.config_criticalBatteryWarningLevel); final ContentResolver resolver = mContext.getContentResolver(); - int defWarnLevel = mContext.getResources().getInteger( + final int defWarnLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_lowBatteryWarningLevel); - int warnLevel = Settings.Global.getInt(resolver, + final int lowPowerModeTriggerLevel = Settings.Global.getInt(resolver, Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, defWarnLevel); + + // Note LOW_POWER_MODE_TRIGGER_LEVEL can take any value between 0 and 100, but + // for the UI purposes, let's cap it at 15% -- i.e. even if the trigger level is higher + // like 50%, let's not show the "low battery" notification until it hits + // config_lowBatteryWarningLevel, which is 15% by default. + // LOW_POWER_MODE_TRIGGER_LEVEL is still used in other places as-is. For example, if it's + // 50, then battery saver kicks in when the battery level hits 50%. + int warnLevel = Math.min(defWarnLevel, lowPowerModeTriggerLevel); + if (warnLevel == 0) { warnLevel = defWarnLevel; } @@ -196,6 +220,12 @@ public class PowerUI extends SystemUI { final boolean plugged = mPlugType != 0; final boolean oldPlugged = oldPlugType != 0; + // if we are now unplugged but we were previously plugged in we should allow the + // time based trigger again. + if (!plugged && plugged != oldPlugged) { + mLowWarningShownThisChargeCycle = false; + mSevereWarningShownThisChargeCycle = false; + } int oldBucket = findBatteryLevelBucket(oldBatteryLevel); int bucket = findBatteryLevelBucket(mBatteryLevel); @@ -224,21 +254,11 @@ public class PowerUI extends SystemUI { return; } - boolean isPowerSaver = mPowerManager.isPowerSaveMode(); - if (!plugged - && !isPowerSaver - && (bucket < oldBucket || oldPlugged) - && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN - && bucket < 0) { - - // only play SFX when the dialog comes up or the bucket changes - final boolean playSound = bucket != oldBucket || oldPlugged; - mWarnings.showLowBatteryWarning(playSound); - } else if (isPowerSaver || plugged || (bucket > oldBucket && bucket > 0)) { - mWarnings.dismissLowBatteryWarning(); - } else { - mWarnings.updateLowBatteryWarning(); - } + // Show the correct version of low battery warning if needed + ThreadUtils.postOnBackgroundThread(() -> { + maybeShowBatteryWarning(plugged, oldPlugged, oldBucket, bucket); + }); + } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { mScreenOffTime = SystemClock.elapsedRealtime(); } else if (Intent.ACTION_SCREEN_ON.equals(action)) { @@ -249,7 +269,83 @@ public class PowerUI extends SystemUI { Slog.w(TAG, "unknown intent: " + intent); } } - }; + } + + protected void maybeShowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket, + int bucket) { + boolean isPowerSaver = mPowerManager.isPowerSaveMode(); + // only play SFX when the dialog comes up or the bucket changes + final boolean playSound = bucket != oldBucket || oldPlugged; + if (mEnhancedEstimates.isHybridNotificationEnabled()) { + final Estimate estimate = mEnhancedEstimates.getEstimate(); + // Turbo is not always booted once SysUI is running so we have ot make sure we actually + // get data back + if (estimate != null) { + mTimeRemaining = estimate.estimateMillis; + mWarnings.updateEstimate(estimate); + mWarnings.updateThresholds(mEnhancedEstimates.getLowWarningThreshold(), + mEnhancedEstimates.getSevereWarningThreshold()); + } + } + + if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket, + mTimeRemaining, isPowerSaver, mBatteryStatus)) { + mWarnings.showLowBatteryWarning(playSound); + + // mark if we've already shown a warning this cycle. This will prevent the time based + // trigger from spamming users since the time remaining can vary based on current + // device usage. + if (mTimeRemaining < mEnhancedEstimates.getSevereWarningThreshold()) { + mSevereWarningShownThisChargeCycle = true; + } else { + mLowWarningShownThisChargeCycle = true; + } + } else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining, + isPowerSaver)) { + mWarnings.dismissLowBatteryWarning(); + } else { + mWarnings.updateLowBatteryWarning(); + } + } + + @VisibleForTesting + boolean shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket, + int bucket, long timeRemaining, boolean isPowerSaver, int mBatteryStatus) { + return !plugged + && !isPowerSaver + && (((bucket < oldBucket || oldPlugged) && bucket < 0) + || isTimeBasedTrigger(timeRemaining)) + && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN; + } + + @VisibleForTesting + boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket, + long timeRemaining, boolean isPowerSaver) { + final boolean hybridWouldDismiss = mEnhancedEstimates.isHybridNotificationEnabled() + && timeRemaining > mEnhancedEstimates.getLowWarningThreshold(); + final boolean standardWouldDismiss = (bucket > oldBucket && bucket > 0); + return isPowerSaver + || plugged + || (standardWouldDismiss && (!mEnhancedEstimates.isHybridNotificationEnabled() + || hybridWouldDismiss)); + } + + private boolean isTimeBasedTrigger(long timeRemaining) { + if (!mEnhancedEstimates.isHybridNotificationEnabled()) { + return false; + } + + // Only show the time based warning once per charge cycle + final boolean canShowWarning = timeRemaining < mEnhancedEstimates.getLowWarningThreshold() + && !mLowWarningShownThisChargeCycle; + + // Only show the severe time based warning once per charge cycle + final boolean canShowSevereWarning = + timeRemaining < mEnhancedEstimates.getSevereWarningThreshold() + && !mSevereWarningShownThisChargeCycle; + + return canShowWarning || canShowSevereWarning; + } private void initTemperatureWarning() { ContentResolver resolver = mContext.getContentResolver(); @@ -263,7 +359,7 @@ public class PowerUI extends SystemUI { resources.getInteger(R.integer.config_warningTemperature)); if (mThresholdTemp < 0f) { - // Get the throttling temperature. No need to check if we're not throttling. + // Get the shutdown temperature, adjust for warning tolerance. float[] throttlingTemps = mHardwarePropertiesManager.getDeviceTemperatures( HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN, HardwarePropertiesManager.TEMPERATURE_SHUTDOWN); @@ -276,6 +372,25 @@ public class PowerUI extends SystemUI { resources.getInteger(R.integer.config_warningTemperatureTolerance); } + if (mThermalService == null) { + // Enable push notifications of throttling from vendor thermal + // management subsystem via thermalservice, in addition to our + // usual polling, to react to temperature jumps more quickly. + IBinder b = ServiceManager.getService("thermalservice"); + + if (b != null) { + mThermalService = IThermalService.Stub.asInterface(b); + try { + mThermalService.registerThermalEventListener( + new ThermalEventListener()); + } catch (RemoteException e) { + // Should never happen. + } + } else { + Slog.w(TAG, "cannot find thermalservice, no throttling push notifications"); + } + } + setNextLogTime(); // This initialization method may be called on a configuration change. Only one set of @@ -402,6 +517,8 @@ public class PowerUI extends SystemUI { public interface WarningsUI { void update(int batteryLevel, int bucket, long screenOffTime); + void updateEstimate(Estimate estimate); + void updateThresholds(long lowThreshold, long severeThreshold); void dismissLowBatteryWarning(); void showLowBatteryWarning(boolean playSound); void dismissInvalidChargerWarning(); @@ -414,5 +531,15 @@ public class PowerUI extends SystemUI { void dump(PrintWriter pw); void userSwitched(); } -} + // Thermal event received from vendor thermal management subsystem + private final class ThermalEventListener extends IThermalEventListener.Stub { + @Override public void notifyThrottling(boolean isThrottling, Temperature temp) { + // Trigger an update of the temperature warning. Only one + // callback can be enabled at a time, so remove any existing + // callback; updateTemperatureWarning will schedule another one. + mHandler.removeCallbacks(mUpdateTempCallback); + updateTemperatureWarning(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java index f960dc5b4a47..2a2bc0923e1b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java +++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java @@ -51,10 +51,11 @@ public class AutoAddTracker { public AutoAddTracker(Context context) { mContext = context; mAutoAdded = new ArraySet<>(getAdded()); + // TODO: remove migration code and shared preferences keys after P release for (String[] convertPref : CONVERT_PREFS) { if (Prefs.getBoolean(context, convertPref[0], false)) { setTileAdded(convertPref[1]); - Prefs.putBoolean(context, convertPref[0], false); + Prefs.remove(context, convertPref[0]); } } mContext.getContentResolver().registerContentObserver( diff --git a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java index 5f2609380085..e7eefe8d5e56 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java @@ -19,12 +19,12 @@ import android.graphics.drawable.Drawable; import android.service.quicksettings.Tile; import android.widget.ImageView; +import com.android.settingslib.graph.SignalDrawable; import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.plugins.qs.QSTile.Icon; import com.android.systemui.plugins.qs.QSTile.State; import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.statusbar.phone.SignalDrawable; import java.util.Objects; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index 8f1880039857..222c6e8274f5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -20,11 +20,12 @@ import android.view.View.OnAttachStateChangeListener; import android.view.View.OnLayoutChangeListener; import com.android.systemui.Dependency; -import com.android.systemui.plugins.qs.*; +import com.android.systemui.plugins.qs.QS; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.PagedTileLayout.PageListener; -import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.QSHost.Callback; +import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.TouchAnimator.Builder; import com.android.systemui.qs.TouchAnimator.Listener; import com.android.systemui.tuner.TunerService; @@ -44,7 +45,11 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha public static final float EXPANDED_TILE_DELAY = .86f; private final ArrayList<View> mAllViews = new ArrayList<>(); - private final ArrayList<View> mTopFiveQs = new ArrayList<>(); + /** + * List of {@link View}s representing Quick Settings that are being animated from the quick QS + * position to the normal QS panel. + */ + private final ArrayList<View> mQuickQsViews = new ArrayList<>(); private final QuickQSPanel mQuickQsPanel; private final QSPanel mQsPanel; private final QS mQs; @@ -57,6 +62,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private TouchAnimator mTranslationXAnimator; private TouchAnimator mTranslationYAnimator; private TouchAnimator mNonfirstPageAnimator; + private TouchAnimator mNonfirstPageDelayedAnimator; private TouchAnimator mBrightnessAnimator; private boolean mOnKeyguard; @@ -79,10 +85,10 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha QSTileLayout tileLayout = mQsPanel.getTileLayout(); if (tileLayout instanceof PagedTileLayout) { mPagedLayout = ((PagedTileLayout) tileLayout); - mPagedLayout.setPageListener(this); } else { Log.w(TAG, "QS Not using page layout"); } + panel.setPageListener(this); } public void onRtlChanged() { @@ -157,7 +163,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha clearAnimationState(); mAllViews.clear(); - mTopFiveQs.clear(); + mQuickQsViews.clear(); QSTileLayout tileLayout = mQsPanel.getTileLayout(); mAllViews.add((View) tileLayout); @@ -198,7 +204,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0); translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0); - mTopFiveQs.add(tileView.getIcon()); + mQuickQsViews.add(tileView.getIconWithBackground()); mAllViews.add(tileView.getIcon()); mAllViews.add(quickTileView); } else if (mFullRows && isIconInAnimatedRow(count)) { @@ -244,10 +250,8 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha mFirstPageDelayedAnimator = new TouchAnimator.Builder() .setStartDelay(EXPANDED_TILE_DELAY) .addFloat(tileLayout, "alpha", 0, 1) - .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1) .addFloat(mQsPanel.getDivider(), "alpha", 0, 1) .addFloat(mQsPanel.getFooter().getView(), "alpha", 0, 1).build(); - mAllViews.add(mQsPanel.getPageIndicator()); mAllViews.add(mQsPanel.getDivider()); mAllViews.add(mQsPanel.getFooter().getView()); float px = 0; @@ -265,11 +269,13 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } mNonfirstPageAnimator = new TouchAnimator.Builder() .addFloat(mQuickQsPanel, "alpha", 1, 0) - .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1) .addFloat(mQsPanel.getDivider(), "alpha", 0, 1) .setListener(mNonFirstPageListener) .setEndDelay(.5f) .build(); + mNonfirstPageDelayedAnimator = new TouchAnimator.Builder() + .setStartDelay(.14f) + .addFloat(tileLayout, "alpha", 0, 1).build(); } private boolean isIconInAnimatedRow(int count) { @@ -314,6 +320,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } } else { mNonfirstPageAnimator.setPosition(position); + mNonfirstPageDelayedAnimator.setPosition(position); } } @@ -325,9 +332,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha @Override public void onAnimationAtEnd() { mQuickQsPanel.setVisibility(View.INVISIBLE); - final int N = mTopFiveQs.size(); + final int N = mQuickQsViews.size(); for (int i = 0; i < N; i++) { - mTopFiveQs.get(i).setVisibility(View.VISIBLE); + mQuickQsViews.get(i).setVisibility(View.VISIBLE); } } @@ -335,9 +342,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha public void onAnimationStarted() { mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE); if (mOnFirstPage) { - final int N = mTopFiveQs.size(); + final int N = mQuickQsViews.size(); for (int i = 0; i < N; i++) { - mTopFiveQs.get(i).setVisibility(View.INVISIBLE); + mQuickQsViews.get(i).setVisibility(View.INVISIBLE); } } } @@ -351,9 +358,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha v.setTranslationX(0); v.setTranslationY(0); } - final int N2 = mTopFiveQs.size(); + final int N2 = mQuickQsViews.size(); for (int i = 0; i < N2; i++) { - mTopFiveQs.get(i).setVisibility(View.VISIBLE); + mQuickQsViews.get(i).setVisibility(View.VISIBLE); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 33b5268e03e1..6ccb81772db9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -17,13 +17,18 @@ package com.android.systemui.qs; import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Path; import android.graphics.Point; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; +import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.qs.customize.QSCustomizer; +import com.android.systemui.statusbar.ExpandableOutlineView; /** * Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader} @@ -39,7 +44,8 @@ public class QSContainerImpl extends FrameLayout { protected float mQsExpansion; private QSCustomizer mQSCustomizer; private View mQSFooter; - private float mFullElevation; + private View mBackground; + private int mSideMargins; public QSContainerImpl(Context context, AttributeSet attrs) { super(context, attrs); @@ -53,10 +59,12 @@ public class QSContainerImpl extends FrameLayout { mHeader = findViewById(R.id.header); mQSCustomizer = findViewById(R.id.qs_customize); mQSFooter = findViewById(R.id.qs_footer); - mFullElevation = mQSPanel.getElevation(); + mBackground = findViewById(R.id.quick_settings_background); + mSideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); setClickable(true); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); + setMargins(); } @Override @@ -68,11 +76,22 @@ public class QSContainerImpl extends FrameLayout { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + getDisplay().getRealSize(mSizePoint); + // Since we control our own bottom, be whatever size we want. // Otherwise the QSPanel ends up with 0 height when the window is only the // size of the status bar. - mQSPanel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED)); + Configuration config = getResources().getConfiguration(); + boolean navBelow = config.smallestScreenWidthDp >= 600 + || config.orientation != Configuration.ORIENTATION_LANDSCAPE; + MarginLayoutParams params = (MarginLayoutParams) mQSPanel.getLayoutParams(); + int maxQs = mSizePoint.y - params.topMargin - params.bottomMargin - getPaddingBottom() + - getResources().getDimensionPixelSize(R.dimen.qs_notif_collapsed_space); + if (navBelow) { + maxQs -= getResources().getDimensionPixelSize(R.dimen.navigation_bar_height); + } + mQSPanel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST)); + int width = mQSPanel.getMeasuredWidth(); LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams(); int height = layoutParams.topMargin + layoutParams.bottomMargin @@ -82,7 +101,6 @@ public class QSContainerImpl extends FrameLayout { // QSCustomizer will always be the height of the screen, but do this after // other measuring to avoid changing the height of the QS. - getDisplay().getRealSize(mSizePoint); mQSCustomizer.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(mSizePoint.y, MeasureSpec.EXACTLY)); } @@ -110,6 +128,8 @@ public class QSContainerImpl extends FrameLayout { mQSDetail.setBottom(getTop() + height); // Pin QS Footer to the bottom of the panel. mQSFooter.setTranslationY(height - mQSFooter.getHeight()); + mBackground.setTop(mQSPanel.getTop()); + mBackground.setBottom(height); } protected int calculateContainerHeight() { @@ -123,4 +143,19 @@ public class QSContainerImpl extends FrameLayout { mQsExpansion = expansion; updateExpansion(); } + + private void setMargins() { + setMargins(mQSDetail); + setMargins(mBackground); + setMargins(mQSFooter); + setMargins(mQSPanel); + setMargins(mHeader); + setMargins(mQSCustomizer); + } + + private void setMargins(View view) { + FrameLayout.LayoutParams lp = (LayoutParams) view.getLayoutParams(); + lp.rightMargin = mSideMargins; + lp.leftMargin = mSideMargins; + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java index 8869e8dd3821..ddd991023273 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java @@ -185,10 +185,10 @@ public class QSDetailItems extends FrameLayout { } view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE); final ImageView iv = (ImageView) view.findViewById(android.R.id.icon); - if (item.iconDrawable != null) { - iv.setImageDrawable(item.iconDrawable.getDrawable(iv.getContext())); + if (item.icon != null) { + iv.setImageDrawable(item.icon.getDrawable(iv.getContext())); } else { - iv.setImageResource(item.icon); + iv.setImageResource(item.iconResId); } iv.getOverlay().clear(); if (item.overlay != null) { @@ -258,8 +258,8 @@ public class QSDetailItems extends FrameLayout { } public static class Item { - public int icon; - public QSTile.Icon iconDrawable; + public int iconResId; + public QSTile.Icon icon; public Drawable overlay; public CharSequence line1; public CharSequence line2; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java index 3199decb3ec1..fe3ffb926305 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java @@ -16,20 +16,15 @@ package com.android.systemui.qs; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE; +import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; -import android.app.ActivityManager; -import android.app.AlarmManager; -import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.PorterDuff.Mode; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; import android.os.UserManager; -import android.provider.AlarmClock; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.util.AttributeSet; @@ -37,112 +32,99 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; import android.widget.Toast; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; -import com.android.keyguard.KeyguardStatusView; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.Utils; import com.android.settingslib.drawable.UserIconDrawable; import com.android.systemui.Dependency; -import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import com.android.systemui.R.dimen; -import com.android.systemui.R.id; +import com.android.systemui.SysUiServiceProvider; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.TouchAnimator.Builder; -import com.android.systemui.qs.TouchAnimator.Listener; -import com.android.systemui.qs.TouchAnimator.ListenerAdapter; -import com.android.systemui.statusbar.phone.ExpandableIndicator; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.MultiUserSwitch; import com.android.systemui.statusbar.phone.SettingsButton; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; -import com.android.systemui.statusbar.policy.NextAlarmController; -import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener; import com.android.systemui.tuner.TunerService; public class QSFooterImpl extends FrameLayout implements QSFooter, - NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener, - SignalCallback { - private static final float EXPAND_INDICATOR_THRESHOLD = .93f; + OnClickListener, OnUserInfoChangedListener, EmergencyListener, + SignalCallback, CommandQueue.Callbacks { private ActivityStarter mActivityStarter; - private NextAlarmController mNextAlarmController; private UserInfoController mUserInfoController; private SettingsButton mSettingsButton; protected View mSettingsContainer; + private View mCarrierText; - private TextView mAlarmStatus; - private View mAlarmStatusCollapsed; - private View mDate; - + private boolean mQsDisabled; private QSPanel mQsPanel; private boolean mExpanded; - private boolean mAlarmShowing; - - protected ExpandableIndicator mExpandIndicator; private boolean mListening; - private AlarmManager.AlarmClockInfo mNextAlarm; private boolean mShowEmergencyCallsOnly; + private View mDivider; protected MultiUserSwitch mMultiUserSwitch; private ImageView mMultiUserAvatar; - protected TouchAnimator mSettingsAlpha; + protected TouchAnimator mFooterAnimator; private float mExpansionAmount; protected View mEdit; - private TouchAnimator mAnimator; - private View mDateTimeGroup; - private boolean mKeyguardShowing; - private TouchAnimator mAlarmAnimator; + private TouchAnimator mSettingsCogAnimator; + + private View mActionsContainer; + private View mDragHandle; + private final int mDragHandleExpandOffset; + private View mBackground; public QSFooterImpl(Context context, AttributeSet attrs) { super(context, attrs); + + mDragHandleExpandOffset = getResources(). + getDimensionPixelSize(R.dimen.qs_footer_drag_handle_offset); + } @Override protected void onFinishInflate() { super.onFinishInflate(); - Resources res = getResources(); - + mBackground = findViewById(R.id.qs_footer_background); + mDivider = findViewById(R.id.qs_footer_divider); mEdit = findViewById(android.R.id.edit); mEdit.setOnClickListener(view -> Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> mQsPanel.showEdit(view))); - mDateTimeGroup = findViewById(id.date_time_alarm_group); - mDate = findViewById(R.id.date); - - mExpandIndicator = findViewById(R.id.expand_indicator); mSettingsButton = findViewById(R.id.settings_button); mSettingsContainer = findViewById(R.id.settings_button_container); mSettingsButton.setOnClickListener(this); - mAlarmStatusCollapsed = findViewById(R.id.alarm_status_collapsed); - mAlarmStatus = findViewById(R.id.alarm_status); - mDateTimeGroup.setOnClickListener(this); + mCarrierText = findViewById(R.id.qs_carrier_text); mMultiUserSwitch = findViewById(R.id.multi_user_switch); mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar); + mDragHandle = findViewById(R.id.qs_drag_handle_view); + mActionsContainer = findViewById(R.id.qs_footer_actions_container); + // RenderThread is doing more harm than good when touching the header (to expand quick // settings), so disable it for this view ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true); - ((RippleDrawable) mExpandIndicator.getBackground()).setForceSoftware(true); updateResources(); - mNextAlarmController = Dependency.get(NextAlarmController.class); mUserInfoController = Dependency.get(UserInfoController.class); mActivityStarter = Dependency.get(ActivityStarter.class); addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, @@ -156,33 +138,12 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, int remaining = (width - numTiles * size) / (numTiles - 1); int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space); - mAnimator = new Builder() + mSettingsCogAnimator = new Builder() .addFloat(mSettingsContainer, "translationX", isLayoutRtl() ? (remaining - defSpace) : -(remaining - defSpace), 0) .addFloat(mSettingsButton, "rotation", -120, 0) .build(); - if (mAlarmShowing) { - int translate = isLayoutRtl() ? mDate.getWidth() : -mDate.getWidth(); - mAlarmAnimator = new Builder().addFloat(mDate, "alpha", 1, 0) - .addFloat(mDateTimeGroup, "translationX", 0, translate) - .addFloat(mAlarmStatus, "alpha", 0, 1) - .setListener(new ListenerAdapter() { - @Override - public void onAnimationAtStart() { - mAlarmStatus.setVisibility(View.GONE); - } - - @Override - public void onAnimationStarted() { - mAlarmStatus.setVisibility(View.VISIBLE); - } - }).build(); - } else { - mAlarmAnimator = null; - mAlarmStatus.setVisibility(View.GONE); - mDate.setAlpha(1); - mDateTimeGroup.setTranslationX(0); - } + setExpansion(mExpansionAmount); } @@ -199,40 +160,26 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, } private void updateResources() { - FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size); - - updateSettingsAnimator(); + updateFooterAnimator(); } - private void updateSettingsAnimator() { - mSettingsAlpha = createSettingsAlphaAnimator(); - - final boolean isRtl = isLayoutRtl(); - if (isRtl && mDate.getWidth() == 0) { - mDate.addOnLayoutChangeListener(new OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - mDate.setPivotX(getWidth()); - mDate.removeOnLayoutChangeListener(this); - } - }); - } else { - mDate.setPivotX(isRtl ? mDate.getWidth() : 0); - } + private void updateFooterAnimator() { + mFooterAnimator = createFooterAnimator(); } @Nullable - private TouchAnimator createSettingsAlphaAnimator() { + private TouchAnimator createFooterAnimator() { return new TouchAnimator.Builder() - .addFloat(mEdit, "alpha", 0, 1) - .addFloat(mMultiUserSwitch, "alpha", 0, 1) + .addFloat(mBackground, "alpha", 0, 0.90f) + .addFloat(mDivider, "alpha", 0, 1) + .addFloat(mCarrierText, "alpha", 0, 1) + .addFloat(mActionsContainer, "alpha", 0, 1) + .addFloat(mDragHandle, "translationY", 0, -mDragHandleExpandOffset) .build(); } @Override public void setKeyguardShowing(boolean keyguardShowing) { - mKeyguardShowing = keyguardShowing; setExpansion(mExpansionAmount); } @@ -244,50 +191,29 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, } @Override - public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) { - mNextAlarm = nextAlarm; - if (nextAlarm != null) { - String alarmString = KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm); - mAlarmStatus.setText(alarmString); - mAlarmStatus.setContentDescription(mContext.getString( - R.string.accessibility_quick_settings_alarm, alarmString)); - mAlarmStatusCollapsed.setContentDescription(mContext.getString( - R.string.accessibility_quick_settings_alarm, alarmString)); - } - if (mAlarmShowing != (nextAlarm != null)) { - mAlarmShowing = nextAlarm != null; - updateAnimator(getWidth()); - updateEverything(); - } - } - - @Override public void setExpansion(float headerExpansionFraction) { mExpansionAmount = headerExpansionFraction; - if (mAnimator != null) mAnimator.setPosition(headerExpansionFraction); - if (mAlarmAnimator != null) mAlarmAnimator.setPosition( - mKeyguardShowing ? 0 : headerExpansionFraction); + if (mSettingsCogAnimator != null) mSettingsCogAnimator.setPosition(headerExpansionFraction); - if (mSettingsAlpha != null) { - mSettingsAlpha.setPosition(headerExpansionFraction); + if (mFooterAnimator != null) { + mFooterAnimator.setPosition(headerExpansionFraction); } + } - updateAlarmVisibilities(); - - mExpandIndicator.setExpanded(headerExpansionFraction > EXPAND_INDICATOR_THRESHOLD); + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this); } @Override @VisibleForTesting public void onDetachedFromWindow() { setListening(false); + SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this); super.onDetachedFromWindow(); } - private void updateAlarmVisibilities() { - mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.GONE); - } - @Override public void setListening(boolean listening) { if (listening == mListening) { @@ -302,6 +228,14 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, return findViewById(R.id.expand_indicator); } + @Override + public void disable(int state1, int state2, boolean animate) { + final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0; + if (disabled == mQsDisabled) return; + mQsDisabled = disabled; + updateEverything(); + } + public void updateEverything() { post(() -> { updateVisibilities(); @@ -310,12 +244,15 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, } private void updateVisibilities() { - updateAlarmVisibilities(); + mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE); mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility( TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE); + final boolean isDemo = UserManager.isDeviceInDemoMode(mContext); - mMultiUserSwitch.setVisibility(mExpanded && mMultiUserSwitch.hasMultipleUsers() && !isDemo + + mMultiUserSwitch.setVisibility(mExpanded + && UserManager.get(mContext).isUserSwitcherEnabled() ? View.VISIBLE : View.INVISIBLE); mEdit.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE); @@ -323,14 +260,12 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, private void updateListeners() { if (mListening) { - mNextAlarmController.addCallback(this); mUserInfoController.addCallback(this); if (Dependency.get(NetworkController.class).hasVoiceCallingFeature()) { Dependency.get(NetworkController.class).addEmergencyListener(this); Dependency.get(NetworkController.class).addCallback(this); } } else { - mNextAlarmController.removeCallback(this); mUserInfoController.removeCallback(this); Dependency.get(NetworkController.class).removeEmergencyListener(this); Dependency.get(NetworkController.class).removeCallback(this); @@ -347,6 +282,11 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, @Override public void onClick(View v) { + // Don't do anything until view are unhidden + if (!mExpanded) { + return; + } + if (v == mSettingsButton) { if (!Dependency.get(DeviceProvisionedController.class).isCurrentUserSetup()) { // If user isn't setup just unlock the device and dump them back at SUW. @@ -374,16 +314,6 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, } else { startSettingsActivity(); } - } else if (v == mDateTimeGroup) { - Dependency.get(MetricsLogger.class).action(ACTION_QS_DATE, - mNextAlarm != null); - if (mNextAlarm != null) { - PendingIntent showIntent = mNextAlarm.getShowIntent(); - mActivityStarter.startPendingIntentDismissingKeyguard(showIntent); - } else { - mActivityStarter.postStartActivityDismissingKeyguard(new Intent( - AlarmClock.ACTION_SHOW_ALARMS), 0); - } } } @@ -406,7 +336,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, @Override public void onUserInfoChanged(String name, Drawable picture, String userAccount) { if (picture != null && - UserManager.get(mContext).isGuestUser(ActivityManager.getCurrentUser()) && + UserManager.get(mContext).isGuestUser(KeyguardUpdateMonitor.getCurrentUser()) && !(picture instanceof UserIconDrawable)) { picture = picture.getConstantState().newDrawable(mContext.getResources()).mutate(); picture.setColorFilter( diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 4a91ee026953..d8e10516fe69 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -25,6 +25,7 @@ import android.support.annotation.VisibleForTesting; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; @@ -63,11 +64,12 @@ public class QSFragment extends Fragment implements QS { private QSContainerImpl mContainer; private int mLayoutDirection; private QSFooter mFooter; + private float mLastQSExpansion = -1; @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { - inflater =inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.qs_theme)); + inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.qs_theme)); return inflater.inflate(R.layout.qs_panel, container, false); } @@ -206,6 +208,11 @@ public class QSFragment extends Fragment implements QS { } @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + return isCustomizing() || mQSPanel.onInterceptTouchEvent(event); + } + + @Override public void setHeaderClickable(boolean clickable) { if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable); @@ -227,6 +234,7 @@ public class QSFragment extends Fragment implements QS { public void setKeyguardShowing(boolean keyguardShowing) { if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing); mKeyguardShowing = keyguardShowing; + mLastQSExpansion = -1; if (mQSAnimator != null) { mQSAnimator.setOnKeyguard(keyguardShowing); @@ -268,22 +276,31 @@ public class QSFragment extends Fragment implements QS { getView().setTranslationY(mKeyguardShowing ? (translationScaleY * height) : headerTranslation); } + if (expansion == mLastQSExpansion) { + return; + } + mLastQSExpansion = expansion; mHeader.setExpansion(mKeyguardShowing ? 1 : expansion); mFooter.setExpansion(mKeyguardShowing ? 1 : expansion); int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom() + mFooter.getHeight(); mQSPanel.setTranslationY(translationScaleY * heightDiff); - mQSDetail.setFullyExpanded(expansion == 1); + boolean fullyExpanded = expansion == 1; + mQSDetail.setFullyExpanded(fullyExpanded); + if (fullyExpanded) { + // Always draw within the bounds of the view when fully expanded. + mQSPanel.setClipBounds(null); + } else { + // Set bounds on the QS panel so it doesn't run over the header when animating. + mQsBounds.top = (int) -mQSPanel.getTranslationY(); + mQsBounds.right = mQSPanel.getWidth(); + mQsBounds.bottom = mQSPanel.getHeight(); + mQSPanel.setClipBounds(mQsBounds); + } if (mQSAnimator != null) { mQSAnimator.setPosition(expansion); } - - // Set bounds on the QS panel so it doesn't run over the header. - mQsBounds.top = (int) -mQSPanel.getTranslationY(); - mQsBounds.right = mQSPanel.getWidth(); - mQsBounds.bottom = mQSPanel.getHeight(); - mQSPanel.setClipBounds(mQsBounds); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 8f41084b2b2c..f7c388db0840 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -26,10 +26,11 @@ import android.metrics.LogMaker; import android.os.Handler; import android.os.Message; import android.service.quicksettings.Tile; +import android.support.v4.widget.Space; import android.util.AttributeSet; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; -import android.widget.ImageView; import android.widget.LinearLayout; import com.android.internal.logging.MetricsLogger; @@ -62,11 +63,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>(); protected final View mBrightnessView; private final H mHandler = new H(); - private final View mPageIndicator; private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); - private int mPanelPaddingBottom; - private int mBrightnessPaddingTop; protected boolean mExpanded; protected boolean mListening; @@ -77,6 +75,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne protected QSSecurityFooter mFooter; private boolean mGridContentVisible = true; + private QSScrollLayout mScrollLayout; protected QSTileLayout mTileLayout; private QSCustomizer mCustomizePanel; @@ -95,18 +94,15 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne setOrientation(VERTICAL); - mBrightnessView = LayoutInflater.from(context).inflate( - R.layout.quick_settings_brightness_dialog, this, false); - addView(mBrightnessView); - - setupTileLayout(); - - mPageIndicator = LayoutInflater.from(context).inflate( - R.layout.qs_page_indicator, this, false); - addView(mPageIndicator); - if (mTileLayout instanceof PagedTileLayout) { - ((PagedTileLayout) mTileLayout).setPageIndicator((PageIndicator) mPageIndicator); - } + mBrightnessView = LayoutInflater.from(mContext).inflate( + R.layout.quick_settings_brightness_dialog, this, false); + mTileLayout = new TileLayout(mContext); + mTileLayout.setListening(mListening); + Space space = new Space(mContext); + space.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, + mContext.getResources().getDimensionPixelSize(R.dimen.qs_footer_height))); + mScrollLayout = new QSScrollLayout(mContext, mBrightnessView, (View) mTileLayout, space); + addView(mScrollLayout); addDivider(); @@ -131,17 +127,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne return mDivider; } - public View getPageIndicator() { - return mPageIndicator; - } - - protected void setupTileLayout() { - mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate( - R.layout.qs_paged_tile_layout, this, false); - mTileLayout.setListening(mListening); - addView((View) mTileLayout); - } - public boolean isShowingCustomize() { return mCustomizePanel != null && mCustomizePanel.isCustomizing(); } @@ -241,9 +226,13 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne public void updateResources() { final Resources res = mContext.getResources(); - mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom); - mBrightnessPaddingTop = res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top); - setPadding(0, mBrightnessPaddingTop, 0, mPanelPaddingBottom); + mBrightnessView.setPadding( + mBrightnessView.getPaddingLeft(), + res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top), + mBrightnessView.getPaddingRight(), + mBrightnessView.getPaddingBottom()); + setPadding( + 0, 0, 0, res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom)); for (TileRecord r : mRecords) { r.tile.clearState(); } @@ -282,8 +271,11 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne public void setExpanded(boolean expanded) { if (mExpanded == expanded) return; mExpanded = expanded; - if (!mExpanded && mTileLayout instanceof PagedTileLayout) { - ((PagedTileLayout) mTileLayout).setCurrentItem(0, false); + if (!mExpanded) { + if (mTileLayout instanceof PagedTileLayout) { + ((PagedTileLayout) mTileLayout).setCurrentItem(0, false); + } + mScrollLayout.setScrollY(0); } mMetricsLogger.visibility(MetricsEvent.QS_PANEL, mExpanded); if (!mExpanded) { @@ -293,6 +285,20 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } } + public void setPageListener(final PagedTileLayout.PageListener pageListener) { + if (mTileLayout instanceof PagedTileLayout) { + ((PagedTileLayout) mTileLayout).setPageListener(pageListener); + } else { + mScrollLayout.setOnScrollChangeListener(new OnScrollChangeListener() { + @Override + public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, + int oldScrollY) { + pageListener.onPageChanged(scrollY == 0); + } + }); + } + } + public boolean isExpanded() { return mExpanded; } @@ -317,6 +323,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } public void refreshAllTiles() { + mBrightnessController.checkRestrictionAndSetEnabled(); for (TileRecord r : mRecords) { r.tile.refreshState(); } @@ -564,6 +571,11 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne mFooter.showDeviceMonitoringDialog(); } + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + return mExpanded && mScrollLayout.shouldIntercept(event); + } + private class H extends Handler { private static final int SHOW_DETAIL = 1; private static final int SET_TILE_VISIBILITY = 2; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java new file mode 100644 index 000000000000..7b1509dcd173 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java @@ -0,0 +1,198 @@ +/* + * 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.qs; + +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.support.v4.widget.NestedScrollView; +import android.util.Property; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewParent; +import android.widget.LinearLayout; + +import com.android.systemui.R; +import com.android.systemui.qs.touch.OverScroll; +import com.android.systemui.qs.touch.SwipeDetector; + +/** + * Quick setting scroll view containing the brightness slider and the QS tiles. + * + * <p>Call {@link #shouldIntercept(MotionEvent)} from parent views' + * {@link #onInterceptTouchEvent(MotionEvent)} method to determine whether this view should + * consume the touch event. + */ +public class QSScrollLayout extends NestedScrollView { + private final int mTouchSlop; + private final int mFooterHeight; + private int mLastMotionY; + private final SwipeDetector mSwipeDetector; + private final OverScrollHelper mOverScrollHelper; + private float mContentTranslationY; + + public QSScrollLayout(Context context, View... children) { + super(context); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mFooterHeight = getResources().getDimensionPixelSize(R.dimen.qs_footer_height); + LinearLayout linearLayout = new LinearLayout(mContext); + linearLayout.setLayoutParams(new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + linearLayout.setOrientation(LinearLayout.VERTICAL); + for (View view : children) { + linearLayout.addView(view); + } + addView(linearLayout); + setOverScrollMode(OVER_SCROLL_NEVER); + mOverScrollHelper = new OverScrollHelper(); + mSwipeDetector = new SwipeDetector(context, mOverScrollHelper, SwipeDetector.VERTICAL); + mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, true); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (!canScrollVertically(1) && !canScrollVertically(-1)) { + return false; + } + mSwipeDetector.onTouchEvent(ev); + return super.onInterceptTouchEvent(ev) || mOverScrollHelper.isInOverScroll(); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (!canScrollVertically(1) && !canScrollVertically(-1)) { + return false; + } + mSwipeDetector.onTouchEvent(ev); + return super.onTouchEvent(ev); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + canvas.translate(0, mContentTranslationY); + super.dispatchDraw(canvas); + canvas.translate(0, -mContentTranslationY); + } + + public boolean shouldIntercept(MotionEvent ev) { + if (ev.getY() > (getBottom() - mFooterHeight)) { + // Do not intercept touches that are below the divider between QS and the footer. + return false; + } + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { + mLastMotionY = (int) ev.getY(); + } else if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) { + // Do not allow NotificationPanelView to intercept touch events when this + // view can be scrolled down. + if (mLastMotionY >= 0 && Math.abs(ev.getY() - mLastMotionY) > mTouchSlop + && canScrollVertically(1)) { + requestParentDisallowInterceptTouchEvent(true); + mLastMotionY = (int) ev.getY(); + return true; + } + } else if (ev.getActionMasked() == MotionEvent.ACTION_CANCEL + || ev.getActionMasked() == MotionEvent.ACTION_UP) { + mLastMotionY = -1; + requestParentDisallowInterceptTouchEvent(false); + } + return false; + } + + private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) { + final ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(disallowIntercept); + } + } + + private void setContentTranslationY(float contentTranslationY) { + mContentTranslationY = contentTranslationY; + invalidate(); + } + + private static final Property<QSScrollLayout, Float> CONTENT_TRANS_Y = + new Property<QSScrollLayout, Float>(Float.class, "qsScrollLayoutContentTransY") { + @Override + public Float get(QSScrollLayout qsScrollLayout) { + return qsScrollLayout.mContentTranslationY; + } + + @Override + public void set(QSScrollLayout qsScrollLayout, Float y) { + qsScrollLayout.setContentTranslationY(y); + } + }; + + private class OverScrollHelper implements SwipeDetector.Listener { + private boolean mIsInOverScroll; + + // We use this value to calculate the actual amount the user has overscrolled. + private float mFirstDisplacement = 0; + + @Override + public void onDragStart(boolean start) {} + + @Override + public boolean onDrag(float displacement, float velocity) { + // Only overscroll if the user is scrolling down when they're already at the bottom + // or scrolling up when they're already at the top. + boolean wasInOverScroll = mIsInOverScroll; + mIsInOverScroll = (!canScrollVertically(1) && displacement < 0) || + (!canScrollVertically(-1) && displacement > 0); + + if (wasInOverScroll && !mIsInOverScroll) { + // Exit overscroll. This can happen when the user is in overscroll and then + // scrolls the opposite way. Note that this causes the reset translation animation + // to run while the user is dragging, which feels a bit unnatural. + reset(); + } else if (mIsInOverScroll) { + if (Float.compare(mFirstDisplacement, 0) == 0) { + // Because users can scroll before entering overscroll, we need to + // subtract the amount where the user was not in overscroll. + mFirstDisplacement = displacement; + } + float overscrollY = displacement - mFirstDisplacement; + setContentTranslationY(getDampedOverScroll(overscrollY)); + } + + return mIsInOverScroll; + } + + @Override + public void onDragEnd(float velocity, boolean fling) { + reset(); + } + + private void reset() { + if (Float.compare(mContentTranslationY, 0) != 0) { + ObjectAnimator.ofFloat(QSScrollLayout.this, CONTENT_TRANS_Y, 0) + .setDuration(100) + .start(); + } + mIsInOverScroll = false; + mFirstDisplacement = 0; + } + + public boolean isInOverScroll() { + return mIsInOverScroll; + } + + private float getDampedOverScroll(float y) { + return OverScroll.dampedScroll(y, getHeight()); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index 00b883a541db..83148558ea1d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -20,12 +20,13 @@ import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; +import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.Space; import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.plugins.qs.*; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile.SignalState; import com.android.systemui.plugins.qs.QSTile.State; import com.android.systemui.plugins.qs.QSTileView; @@ -43,19 +44,21 @@ public class QuickQSPanel extends QSPanel { public static final String NUM_QUICK_TILES = "sysui_qqs_count"; + private boolean mDisabledByPolicy; private int mMaxTiles; protected QSPanel mFullPanel; public QuickQSPanel(Context context, AttributeSet attrs) { super(context, attrs); if (mFooter != null) { - removeView((View) mFooter.getView()); + removeView(mFooter.getView()); } if (mTileLayout != null) { for (int i = 0; i < mRecords.size(); i++) { mTileLayout.removeTile(mRecords.get(i)); } - removeView((View) mTileLayout); + View tileLayoutView = (View) mTileLayout; + ((ViewGroup) tileLayoutView.getParent()).removeView(tileLayoutView); } mTileLayout = new HeaderTileLayout(context); mTileLayout.setListening(mListening); @@ -151,6 +154,30 @@ public class QuickQSPanel extends QSPanel { return Dependency.get(TunerService.class).getValue(NUM_QUICK_TILES, 6); } + void setDisabledByPolicy(boolean disabled) { + if (disabled != mDisabledByPolicy) { + mDisabledByPolicy = disabled; + setVisibility(disabled ? View.GONE : View.VISIBLE); + } + } + + /** + * Sets the visibility of this {@link QuickQSPanel}. This method has no effect when this panel + * is disabled by policy through {@link #setDisabledByPolicy(boolean)}, and in this case the + * visibility will always be {@link View#GONE}. This method is called externally by + * {@link QSAnimator} only. + */ + @Override + public void setVisibility(int visibility) { + if (mDisabledByPolicy) { + if (getVisibility() == View.GONE) { + return; + } + visibility = View.GONE; + } + super.setVisibility(visibility); + } + private static class HeaderTileLayout extends LinearLayout implements QSTileLayout { protected final ArrayList<TileRecord> mRecords = new ArrayList<>(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 0709e229bd4d..4d7333b99eee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -14,11 +14,16 @@ package com.android.systemui.qs; +import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; +import static android.app.StatusBarManager.DISABLE_NONE; + import android.content.Context; +import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; +import android.provider.AlarmClock; import android.support.annotation.VisibleForTesting; import android.util.AttributeSet; import android.view.View; @@ -30,13 +35,17 @@ import com.android.systemui.BatteryMeterView; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.R.id; +import com.android.systemui.SysUiServiceProvider; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.QSDetail.Callback; -import com.android.systemui.statusbar.SignalClusterView; +import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.phone.StatusBarIconController; +import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager; +import com.android.systemui.statusbar.policy.DarkIconDispatcher; import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver; - -public class QuickStatusBarHeader extends RelativeLayout { +public class QuickStatusBarHeader extends RelativeLayout + implements CommandQueue.Callbacks, View.OnClickListener { private ActivityStarter mActivityStarter; @@ -44,9 +53,16 @@ public class QuickStatusBarHeader extends RelativeLayout { private boolean mExpanded; private boolean mListening; + private boolean mQsDisabled; protected QuickQSPanel mHeaderQsPanel; protected QSTileHost mHost; + private TintedIconManager mIconManager; + private TouchAnimator mAlphaAnimator; + + private View mQuickQsStatusIcons; + + private View mDate; public QuickStatusBarHeader(Context context, AttributeSet attrs) { super(context, attrs); @@ -58,19 +74,27 @@ public class QuickStatusBarHeader extends RelativeLayout { Resources res = getResources(); mHeaderQsPanel = findViewById(R.id.quick_qs_panel); + mDate = findViewById(R.id.date); + mDate.setOnClickListener(this); + mQuickQsStatusIcons = findViewById(R.id.quick_qs_status_icons); + mIconManager = new TintedIconManager(findViewById(R.id.statusIcons)); // RenderThread is doing more harm than good when touching the header (to expand quick // settings), so disable it for this view updateResources(); - // Set the light/dark theming on the header status UI to match the current theme. + Rect tintArea = new Rect(0, 0, 0, 0); int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground); float intensity = colorForeground == Color.WHITE ? 0 : 1; - Rect tintArea = new Rect(0, 0, 0, 0); + int fillColor = fillColorForIntensity(intensity, getContext()); + + // Set light text on the header icons because they will always be on a black background + applyDarkness(R.id.clock, tintArea, 0, DarkIconDispatcher.DEFAULT_ICON_TINT); + applyDarkness(id.signal_cluster, tintArea, intensity, colorForeground); - applyDarkness(R.id.battery, tintArea, intensity, colorForeground); - applyDarkness(R.id.clock, tintArea, intensity, colorForeground); + // Set the correct tint for the status icons so they contrast + mIconManager.setTint(fillColor); BatteryMeterView battery = findViewById(R.id.battery); battery.setForceShowPercent(true); @@ -85,6 +109,13 @@ public class QuickStatusBarHeader extends RelativeLayout { } } + private int fillColorForIntensity(float intensity, Context context) { + if (intensity == 0) { + return context.getColor(R.color.light_mode_icon_color_dual_tone_fill); + } + return context.getColor(R.color.dark_mode_icon_color_dual_tone_fill); + } + @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -98,6 +129,13 @@ public class QuickStatusBarHeader extends RelativeLayout { } private void updateResources() { + updateAlphaAnimator(); + } + + private void updateAlphaAnimator() { + mAlphaAnimator = new TouchAnimator.Builder() + .addFloat(mQuickQsStatusIcons, "alpha", 1, 0) + .build(); } public int getCollapsedHeight() { @@ -116,12 +154,34 @@ public class QuickStatusBarHeader extends RelativeLayout { } public void setExpansion(float headerExpansionFraction) { + if (mAlphaAnimator != null ) { + mAlphaAnimator.setPosition(headerExpansionFraction); + } + } + + @Override + public void disable(int state1, int state2, boolean animate) { + final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0; + if (disabled == mQsDisabled) return; + mQsDisabled = disabled; + mHeaderQsPanel.setDisabledByPolicy(disabled); + final int rawHeight = (int) getResources().getDimension( + com.android.internal.R.dimen.quick_qs_total_height); + getLayoutParams().height = disabled ? (rawHeight - mHeaderQsPanel.getHeight()) : rawHeight; + } + + @Override + public void onAttachedToWindow() { + SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this); + Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager); } @Override @VisibleForTesting public void onDetachedFromWindow() { setListening(false); + SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this); + Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager); super.onDetachedFromWindow(); } @@ -133,6 +193,14 @@ public class QuickStatusBarHeader extends RelativeLayout { mListening = listening; } + @Override + public void onClick(View v) { + if(v == mDate){ + Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(new Intent( + AlarmClock.ACTION_SHOW_ALARMS),0); + } + } + public void updateEverything() { post(() -> setClickable(false)); } @@ -147,6 +215,11 @@ public class QuickStatusBarHeader extends RelativeLayout { //host.setHeaderView(mExpandIndicator); mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this); mHeaderQsPanel.setHost(host, null /* No customization in header */); + + // Use SystemUI context to get battery meter colors, and let it use the default tint (white) + BatteryMeterView battery = findViewById(R.id.battery); + battery.setColorsFromContext(mHost.getContext()); + battery.onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT); } public void setCallback(Callback qsPanelCallback) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java index 9ee40ccf8893..d9583af65df6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java @@ -41,6 +41,7 @@ public class SignalTileView extends QSIconViewImpl { private ImageView mOut; private int mWideOverlayIconStartPadding; + private int mSignalIndicatorToIconFrameSpacing; public SignalTileView(Context context) { super(context); @@ -48,8 +49,13 @@ public class SignalTileView extends QSIconViewImpl { mIn = addTrafficView(R.drawable.ic_qs_signal_in); mOut = addTrafficView(R.drawable.ic_qs_signal_out); + setClipChildren(false); + setClipToPadding(false); + mWideOverlayIconStartPadding = context.getResources().getDimensionPixelSize( R.dimen.wide_type_icon_start_padding_qs); + mSignalIndicatorToIconFrameSpacing = context.getResources().getDimensionPixelSize( + R.dimen.signal_indicator_to_icon_frame_spacing); } private ImageView addTrafficView(int icon) { @@ -99,10 +105,10 @@ public class SignalTileView extends QSIconViewImpl { boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; int left, right; if (isRtl) { - right = mIconFrame.getLeft(); + right = getLeft() - mSignalIndicatorToIconFrameSpacing; left = right - indicator.getMeasuredWidth(); } else { - left = mIconFrame.getRight(); + left = getRight() + mSignalIndicatorToIconFrameSpacing; right = left + indicator.getMeasuredWidth(); } indicator.layout( diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 3b9e7bcfb9b4..65135ab142d7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -104,6 +104,11 @@ public class TileLayout extends ViewGroup implements QSTileLayout { setMeasuredDimension(width, height); } + @Override + public boolean hasOverlappingRendering() { + return false; + } + private static int exactly(int size) { return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java index 142aab2626a6..23d3ebbbfe80 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java @@ -47,7 +47,7 @@ public class CarQSFooter extends RelativeLayout implements QSFooter, private MultiUserSwitch mMultiUserSwitch; private TextView mUserName; private ImageView mMultiUserAvatar; - private UserGridView mUserGridView; + private CarQSFragment.UserSwitchCallback mUserSwitchCallback; public CarQSFooter(Context context, AttributeSet attrs) { super(context, attrs); @@ -63,15 +63,15 @@ public class CarQSFooter extends RelativeLayout implements QSFooter, mUserInfoController = Dependency.get(UserInfoController.class); mMultiUserSwitch.setOnClickListener(v -> { - if (mUserGridView == null) { + if (mUserSwitchCallback == null) { Log.e(TAG, "CarQSFooter not properly set up; cannot display user switcher."); return; } - if (!mUserGridView.isShowing()) { - mUserGridView.show(); + if (!mUserSwitchCallback.isShowing()) { + mUserSwitchCallback.show(); } else { - mUserGridView.hide(); + mUserSwitchCallback.hide(); } }); @@ -102,8 +102,8 @@ public class CarQSFooter extends RelativeLayout implements QSFooter, } } - public void setUserGridView(UserGridView view) { - mUserGridView = view; + public void setUserSwitchCallback(CarQSFragment.UserSwitchCallback callback) { + mUserSwitchCallback = callback; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java index 13298d378845..0ee6d1fb6664 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java @@ -13,6 +13,12 @@ */ package com.android.systemui.qs.car; +import android.animation.Animator; +import android.animation.AnimatorInflater; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.app.Fragment; import android.os.Bundle; import android.support.annotation.Nullable; @@ -26,18 +32,29 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.QSFooter; +import com.android.systemui.statusbar.car.PageIndicator; import com.android.systemui.statusbar.car.UserGridView; import com.android.systemui.statusbar.policy.UserSwitcherController; +import java.util.ArrayList; +import java.util.List; + /** * A quick settings fragment for the car. For auto, there is no row for quick settings or ability * to expand the quick settings panel. Instead, the only thing is that displayed is the * status bar, and a static row with access to the user switcher and settings. */ public class CarQSFragment extends Fragment implements QS { + private ViewGroup mPanel; private View mHeader; + private View mUserSwitcherContainer; private CarQSFooter mFooter; + private View mFooterUserName; + private View mFooterExpandIcon; private UserGridView mUserGridView; + private PageIndicator mPageIndicator; + private AnimatorSet mAnimatorSet; + private UserSwitchCallback mUserSwitchCallback; @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @@ -48,14 +65,26 @@ public class CarQSFragment extends Fragment implements QS { @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + mPanel = (ViewGroup) view; mHeader = view.findViewById(R.id.header); mFooter = view.findViewById(R.id.qs_footer); + mFooterUserName = mFooter.findViewById(R.id.user_name); + mFooterExpandIcon = mFooter.findViewById(R.id.user_switch_expand_icon); + + mUserSwitcherContainer = view.findViewById(R.id.user_switcher_container); + + updateUserSwitcherHeight(0); mUserGridView = view.findViewById(R.id.user_grid); mUserGridView.init(null, Dependency.get(UserSwitcherController.class), - false /* showInitially */); + false /* overrideAlpha */); - mFooter.setUserGridView(mUserGridView); + mPageIndicator = view.findViewById(R.id.user_switcher_page_indicator); + mPageIndicator.setupWithViewPager(mUserGridView); + + mUserSwitchCallback = new UserSwitchCallback(); + mFooter.setUserSwitchCallback(mUserSwitchCallback); + mUserGridView.setUserSwitchCallback(mUserSwitchCallback); } @Override @@ -82,11 +111,13 @@ public class CarQSFragment extends Fragment implements QS { @Override public void setHeaderListening(boolean listening) { mFooter.setListening(listening); + mUserGridView.setListening(listening); } @Override public void setListening(boolean listening) { mFooter.setListening(listening); + mUserGridView.setListening(listening); } @Override @@ -171,4 +202,126 @@ public class CarQSFragment extends Fragment implements QS { public void setExpandClickListener(OnClickListener onClickListener) { // No ability to expand the quick settings. } + + public class UserSwitchCallback { + private boolean mShowing; + + public boolean isShowing() { + return mShowing; + } + + public void show() { + mShowing = true; + animateHeightChange(true /* opening */); + } + + public void hide() { + mShowing = false; + animateHeightChange(false /* opening */); + } + + public void resetShowing() { + if (mShowing) { + for (int i = 0; i < mUserGridView.getChildCount(); i++) { + ViewGroup podContainer = (ViewGroup) mUserGridView.getChildAt(i); + // Need to bring the last child to the front to maintain the order in the pod + // container. Why? ¯\_(ツ)_/¯ + if (podContainer.getChildCount() > 0) { + podContainer.getChildAt(podContainer.getChildCount() - 1).bringToFront(); + } + // The alpha values are default to 0, so if the pods have been refreshed, they + // need to be set to 1 when showing. + for (int j = 0; j < podContainer.getChildCount(); j++) { + podContainer.getChildAt(j).setAlpha(1f); + } + } + } + } + } + + private void updateUserSwitcherHeight(int height) { + ViewGroup.LayoutParams layoutParams = mUserSwitcherContainer.getLayoutParams(); + layoutParams.height = height; + mUserSwitcherContainer.requestLayout(); + } + + private void animateHeightChange(boolean opening) { + // Animation in progress; cancel it to avoid contention. + if (mAnimatorSet != null){ + mAnimatorSet.cancel(); + } + + List<Animator> allAnimators = new ArrayList<>(); + ValueAnimator heightAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(getContext(), + opening ? R.anim.car_user_switcher_open_animation + : R.anim.car_user_switcher_close_animation); + heightAnimator.addUpdateListener(valueAnimator -> { + updateUserSwitcherHeight((Integer) valueAnimator.getAnimatedValue()); + }); + allAnimators.add(heightAnimator); + + // The user grid contains pod containers that each contain a number of pods. Animate + // all pods to avoid any discrepancy/race conditions with possible changes during the + // animation. + int cascadeDelay = getResources().getInteger( + R.integer.car_user_switcher_anim_cascade_delay_ms); + for (int i = 0; i < mUserGridView.getChildCount(); i++) { + ViewGroup podContainer = (ViewGroup) mUserGridView.getChildAt(i); + for (int j = 0; j < podContainer.getChildCount(); j++) { + View pod = podContainer.getChildAt(j); + Animator podAnimator = AnimatorInflater.loadAnimator(getContext(), + opening ? R.anim.car_user_switcher_open_pod_animation + : R.anim.car_user_switcher_close_pod_animation); + // Add the cascading delay between pods + if (opening) { + podAnimator.setStartDelay(podAnimator.getStartDelay() + j * cascadeDelay); + } + podAnimator.setTarget(pod); + allAnimators.add(podAnimator); + } + } + + Animator nameAnimator = AnimatorInflater.loadAnimator(getContext(), + opening ? R.anim.car_user_switcher_open_name_animation + : R.anim.car_user_switcher_close_name_animation); + nameAnimator.setTarget(mFooterUserName); + allAnimators.add(nameAnimator); + + Animator iconAnimator = AnimatorInflater.loadAnimator(getContext(), + opening ? R.anim.car_user_switcher_open_icon_animation + : R.anim.car_user_switcher_close_icon_animation); + iconAnimator.setTarget(mFooterExpandIcon); + allAnimators.add(iconAnimator); + + Animator pageAnimator = AnimatorInflater.loadAnimator(getContext(), + opening ? R.anim.car_user_switcher_open_pages_animation + : R.anim.car_user_switcher_close_pages_animation); + pageAnimator.setTarget(mPageIndicator); + allAnimators.add(pageAnimator); + + mAnimatorSet = new AnimatorSet(); + mAnimatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mAnimatorSet = null; + } + }); + mAnimatorSet.playTogether(allAnimators.toArray(new Animator[0])); + + // Setup all values to the start values in the animations, since there are delays, but need + // to have all values start at the beginning. + setupInitialValues(mAnimatorSet); + + mAnimatorSet.start(); + } + + private void setupInitialValues(Animator anim) { + if (anim instanceof AnimatorSet) { + for (Animator a : ((AnimatorSet) anim).getChildAnimations()) { + setupInitialValues(a); + } + } else if (anim instanceof ObjectAnimator) { + ((ObjectAnimator) anim).setCurrentFraction(0.0f); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index 5a3081cd6664..3847040271e2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -171,8 +171,6 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene queryTiles(); mNotifQsContainer.setCustomizerAnimating(true); mNotifQsContainer.setCustomizerShowing(true); - announceForAccessibility(mContext.getString( - R.string.accessibility_desc_quick_settings_edit)); Dependency.get(KeyguardMonitor.class).addCallback(mKeyguardCallback); updateNavColors(); } @@ -213,8 +211,6 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener); mNotifQsContainer.setCustomizerAnimating(true); mNotifQsContainer.setCustomizerShowing(false); - announceForAccessibility(mContext.getString( - R.string.accessibility_desc_quick_settings)); Dependency.get(KeyguardMonitor.class).removeCallback(mKeyguardCallback); updateNavColors(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 77c3bfab8de9..bf9746eafaf0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -23,6 +23,7 @@ import com.android.systemui.plugins.qs.*; import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.tiles.AirplaneModeTile; +import com.android.systemui.qs.tiles.AlarmTile; import com.android.systemui.qs.tiles.BatterySaverTile; import com.android.systemui.qs.tiles.BluetoothTile; import com.android.systemui.qs.tiles.CastTile; @@ -69,6 +70,7 @@ public class QSFactoryImpl implements QSFactory { else if (tileSpec.equals("saver")) return new DataSaverTile(mHost); else if (tileSpec.equals("night")) return new NightDisplayTile(mHost); else if (tileSpec.equals("nfc")) return new NfcTile(mHost); + else if (tileSpec.equals("alarm")) return new AlarmTile(mHost); // Intent tiles. else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(mHost, tileSpec); else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(mHost, tileSpec); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java index c249e3778c0a..0f83078e0738 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -103,7 +103,7 @@ public class QSIconViewImpl extends QSIconView { if (iv instanceof SlashImageView) { ((SlashImageView) iv).setAnimationEnabled(shouldAnimate); - ((SlashImageView) iv).setState(state.slash, d); + ((SlashImageView) iv).setState(null, d); } else { iv.setImageDrawable(d); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java index 4d0e60d505f6..c9c678c2ad25 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -13,7 +13,12 @@ */ package com.android.systemui.qs.tileimpl; +import static com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH; + +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; @@ -22,16 +27,21 @@ import android.os.Looper; import android.os.Message; import android.service.quicksettings.Tile; import android.text.TextUtils; +import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ImageView.ScaleType; import android.widget.Switch; +import com.android.settingslib.Utils; import com.android.systemui.R; -import com.android.systemui.plugins.qs.*; +import com.android.systemui.plugins.qs.QSIconView; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile.BooleanState; public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { @@ -47,6 +57,12 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { private boolean mCollapsedView; private boolean mClicked; + private final ImageView mBg; + private final int mColorActive; + private final int mColorInactive; + private final int mColorDisabled; + private int mCircleColor; + public QSTileBaseView(Context context, QSIconView icon) { this(context, icon, false); } @@ -60,11 +76,17 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { mIconFrame.setForegroundGravity(Gravity.CENTER); int size = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size); addView(mIconFrame, new LayoutParams(size, size)); + mBg = new ImageView(getContext()); + mBg.setScaleType(ScaleType.FIT_CENTER); + mBg.setImageResource(R.drawable.ic_qs_circle); + mIconFrame.addView(mBg); mIcon = icon; FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); params.setMargins(0, padding, 0, padding); mIconFrame.addView(mIcon, params); + mIconFrame.setClipChildren(false); + mIconFrame.setClipToPadding(false); mTileBackground = newTileBackground(); if (mTileBackground instanceof RippleDrawable) { @@ -73,6 +95,11 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); setBackground(mTileBackground); + mColorActive = Utils.getColorAttr(context, android.R.attr.colorAccent); + mColorDisabled = Utils.getDisabled(context, + Utils.getColorAttr(context, android.R.attr.textColorTertiary)); + mColorInactive = Utils.getColorAttr(context, android.R.attr.textColorSecondary); + setPadding(0, 0, 0, 0); setClipChildren(false); setClipToPadding(false); @@ -80,6 +107,10 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { setFocusable(true); } + public View getBgCicle() { + return mBg; + } + protected Drawable newTileBackground() { final int[] attrs = new int[]{android.R.attr.selectableItemBackgroundBorderless}; final TypedArray ta = getContext().obtainStyledAttributes(attrs); @@ -150,6 +181,20 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } protected void handleStateChanged(QSTile.State state) { + int circleColor = getCircleColor(state.state); + if (circleColor != mCircleColor) { + if (mBg.isShown()) { + ValueAnimator animator = ValueAnimator.ofArgb(mCircleColor, circleColor) + .setDuration(QS_ANIM_LENGTH); + animator.addUpdateListener(animation -> mBg.setImageTintList(ColorStateList.valueOf( + (Integer) animation.getAnimatedValue()))); + animator.start(); + } else { + QSIconViewImpl.setTint(mBg, circleColor); + } + mCircleColor = circleColor; + } + setClickable(state.state != Tile.STATE_UNAVAILABLE); mIcon.setIcon(state); setContentDescription(state.contentDescription); @@ -163,6 +208,19 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } } + private int getCircleColor(int state) { + switch (state) { + case Tile.STATE_ACTIVE: + return mColorActive; + case Tile.STATE_INACTIVE: + case Tile.STATE_UNAVAILABLE: + return mColorDisabled; + default: + Log.e(TAG, "Invalid state " + state); + return 0; + } + } + @Override public void setClickable(boolean clickable) { super.setClickable(clickable); @@ -178,6 +236,10 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { return mIcon; } + public View getIconWithBackground() { + return mIconFrame; + } + @Override public boolean performClick() { mClicked = true; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 576a447447b5..7259282935a0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -373,11 +373,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile { switch (state) { case Tile.STATE_UNAVAILABLE: return Utils.getDisabled(context, - Utils.getColorAttr(context, android.R.attr.colorForeground)); + Utils.getColorAttr(context, android.R.attr.textColorSecondary)); case Tile.STATE_INACTIVE: - return Utils.getColorAttr(context, android.R.attr.textColorHint); + return Utils.getColorAttr(context, android.R.attr.textColorSecondary); case Tile.STATE_ACTIVE: - return Utils.getColorAttr(context, android.R.attr.textColorPrimary); + return Utils.getColorAttr(context, android.R.attr.colorPrimary); default: Log.e("QSTile", "Invalid state " + state); return 0; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java index 263dac0f44ec..9eb9906ba3b5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java @@ -18,6 +18,7 @@ import android.content.Context; import android.content.res.Configuration; import android.service.quicksettings.Tile; import android.text.SpannableStringBuilder; +import android.text.TextUtils; import android.text.style.ForegroundColorSpan; import android.view.Gravity; import android.view.LayoutInflater; @@ -36,8 +37,10 @@ import libcore.util.Objects; /** View that represents a standard quick settings tile. **/ public class QSTileView extends QSTileBaseView { + private static final boolean DUAL_TARGET_ALLOWED = false; private View mDivider; protected TextView mLabel; + private TextView mSecondLine; private ImageView mPadLock; private int mState; private ViewGroup mLabelContainer; @@ -86,6 +89,8 @@ public class QSTileView extends QSTileBaseView { mDivider = mLabelContainer.findViewById(R.id.underline); mExpandIndicator = mLabelContainer.findViewById(R.id.expand_indicator); mExpandSpace = mLabelContainer.findViewById(R.id.expand_space); + mSecondLine = mLabelContainer.findViewById(R.id.app_label); + mSecondLine.setAlpha(.6f); addView(mLabelContainer); } @@ -103,14 +108,20 @@ public class QSTileView extends QSTileBaseView { mState = state.state; mLabel.setText(state.label); } - mExpandIndicator.setVisibility(state.dualTarget ? View.VISIBLE : View.GONE); - mExpandSpace.setVisibility(state.dualTarget ? View.VISIBLE : View.GONE); - mLabelContainer.setContentDescription(state.dualTarget ? state.dualLabelContentDescription + if (!Objects.equal(mSecondLine.getText(), state.secondaryLabel)) { + mSecondLine.setText(state.secondaryLabel); + mSecondLine.setVisibility(TextUtils.isEmpty(state.secondaryLabel) ? View.GONE + : View.VISIBLE); + } + boolean dualTarget = DUAL_TARGET_ALLOWED && state.dualTarget; + mExpandIndicator.setVisibility(dualTarget ? View.VISIBLE : View.GONE); + mExpandSpace.setVisibility(dualTarget ? View.VISIBLE : View.GONE); + mLabelContainer.setContentDescription(dualTarget ? state.dualLabelContentDescription : null); - if (state.dualTarget != mLabelContainer.isClickable()) { - mLabelContainer.setClickable(state.dualTarget); - mLabelContainer.setLongClickable(state.dualTarget); - mLabelContainer.setBackground(state.dualTarget ? newTileBackground() : null); + if (dualTarget != mLabelContainer.isClickable()) { + mLabelContainer.setClickable(dualTarget); + mLabelContainer.setLongClickable(dualTarget); + mLabelContainer.setBackground(dualTarget ? newTileBackground() : null); } mLabel.setEnabled(!state.disabledByPolicy); mPadLock.setVisibility(state.disabledByPolicy ? View.VISIBLE : View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java index bef1aff571f3..9883da6f3a35 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; +import android.os.UserManager; import android.provider.Settings; import android.provider.Settings.Global; import android.service.quicksettings.Tile; @@ -82,6 +83,7 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> { @Override protected void handleUpdateState(BooleanState state, Object arg) { + checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_AIRPLANE_MODE); final int value = arg instanceof Integer ? (Integer)arg : mSetting.getValue(); final boolean airplaneMode = value != 0; state.value = airplaneMode; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java new file mode 100644 index 000000000000..ff3fe731ad4f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.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.qs.tiles; + +import static android.service.quicksettings.Tile.STATE_ACTIVE; +import static android.service.quicksettings.Tile.STATE_UNAVAILABLE; + +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.QS_ALARM; +import static com.android.systemui.keyguard.KeyguardSliceProvider.formatNextAlarm; + +import android.app.AlarmManager.AlarmClockInfo; +import android.app.PendingIntent; +import android.content.Intent; +import android.provider.AlarmClock; + +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.qs.QSTileHost; +import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.statusbar.policy.NextAlarmController; +import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback; + +public class AlarmTile extends QSTileImpl implements NextAlarmChangeCallback { + private final NextAlarmController mController; + private String mNextAlarm; + private PendingIntent mIntent; + + public AlarmTile(QSTileHost host) { + super(host); + mController = Dependency.get(NextAlarmController.class); + } + + @Override + public State newTileState() { + return new BooleanState(); + } + + @Override + protected void handleClick() { + if (mIntent != null) { + Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(mIntent); + } + } + + @Override + protected void handleUpdateState(State state, Object arg) { + state.state = mNextAlarm != null ? STATE_ACTIVE : STATE_UNAVAILABLE; + state.label = getTileLabel(); + state.secondaryLabel = mNextAlarm; + state.icon = ResourceIcon.get(R.drawable.stat_sys_alarm); + ((BooleanState) state).value = mNextAlarm != null; + } + + @Override + public void onNextAlarmChanged(AlarmClockInfo nextAlarm) { + if (nextAlarm != null) { + mNextAlarm = formatNextAlarm(mContext, nextAlarm); + mIntent = nextAlarm.getShowIntent(); + } else { + mNextAlarm = null; + mIntent = null; + } + refreshState(); + } + + @Override + public int getMetricsCategory() { + return QS_ALARM; + } + + @Override + public Intent getLongClickIntent() { + return new Intent(AlarmClock.ACTION_SET_ALARM); + } + + @Override + protected void handleSetListening(boolean listening) { + if (listening) { + mController.addCallback(this); + } else { + mController.removeCallback(this); + } + } + + @Override + public CharSequence getTileLabel() { + return mContext.getString(R.string.status_bar_alarm); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index bc3ccb41cce0..7fe9e3584ee5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -18,7 +18,9 @@ package com.android.systemui.qs.tiles; import static com.android.settingslib.graph.BluetoothDeviceLayerDrawable.createLayerDrawable; +import android.annotation.Nullable; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; @@ -26,7 +28,6 @@ import android.content.Intent; import android.graphics.drawable.Drawable; import android.provider.Settings; import android.service.quicksettings.Tile; -import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; import android.widget.Switch; @@ -35,6 +36,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.graph.BluetoothDeviceLayerDrawable; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; @@ -125,50 +127,80 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { state.slash = new SlashState(); } state.slash.isSlashed = !enabled; + state.label = mContext.getString(R.string.quick_settings_bluetooth_label); + if (enabled) { - state.label = null; if (connected) { - state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_connected); - state.label = mController.getLastDeviceName(); - CachedBluetoothDevice lastDevice = mController.getLastDevice(); - if (lastDevice != null) { - int batteryLevel = lastDevice.getBatteryLevel(); - if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { - state.icon = new BluetoothBatteryDrawable(batteryLevel, - mContext.getResources().getFraction( - R.fraction.bt_battery_scale_fraction, 1, 1)); - } - } + state.icon = new BluetoothConnectedTileIcon(); state.contentDescription = mContext.getString( R.string.accessibility_bluetooth_name, state.label); + + state.label = mController.getLastDeviceName(); } else if (state.isTransient) { state.icon = ResourceIcon.get(R.drawable.ic_bluetooth_transient_animation); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_bluetooth_connecting); - state.label = mContext.getString(R.string.quick_settings_bluetooth_label); } else { state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_bluetooth_on) + "," + mContext.getString(R.string.accessibility_not_connected); } - if (TextUtils.isEmpty(state.label)) { - state.label = mContext.getString(R.string.quick_settings_bluetooth_label); - } state.state = Tile.STATE_ACTIVE; } else { state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on); - state.label = mContext.getString(R.string.quick_settings_bluetooth_label); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_bluetooth_off); state.state = Tile.STATE_INACTIVE; } + state.secondaryLabel = getSecondaryLabel(enabled, connected); + state.dualLabelContentDescription = mContext.getResources().getString( R.string.accessibility_quick_settings_open_settings, getTileLabel()); state.expandedAccessibilityClassName = Switch.class.getName(); } + /** + * Returns the secondary label to use for the given bluetooth connection in the form of the + * battery level or bluetooth profile name. If the bluetooth is disabled, there's no connected + * devices, or we can't map the bluetooth class to a profile, this instead returns {@code null}. + * + * @param enabled whether bluetooth is enabled + * @param connected whether there's a device connected via bluetooth + */ + @Nullable + private String getSecondaryLabel(boolean enabled, boolean connected) { + final CachedBluetoothDevice lastDevice = mController.getLastDevice(); + + if (enabled && connected && lastDevice != null) { + final int batteryLevel = lastDevice.getBatteryLevel(); + + if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { + return mContext.getString( + R.string.quick_settings_bluetooth_secondary_label_battery_level, + Utils.formatPercentage(batteryLevel)); + + } else { + final BluetoothClass bluetoothClass = lastDevice.getBtClass(); + if (bluetoothClass != null) { + if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { + return mContext.getString( + R.string.quick_settings_bluetooth_secondary_label_audio); + } else if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { + return mContext.getString( + R.string.quick_settings_bluetooth_secondary_label_headset); + } else if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_HID)) { + return mContext.getString( + R.string.quick_settings_bluetooth_secondary_label_input); + } + } + } + } + + return null; + } + @Override public int getMetricsCategory() { return MetricsEvent.QS_BLUETOOTH; @@ -212,23 +244,48 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { return new BluetoothDetailAdapter(); } - private class BluetoothBatteryDrawable extends Icon { - private int mLevel; + /** + * Bluetooth icon wrapper for Quick Settings with a battery indicator that reflects the + * connected device's battery level. This is used instead of + * {@link com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon} in order to use a context + * that reflects dark/light theme attributes. + */ + private class BluetoothBatteryTileIcon extends Icon { + private int mBatteryLevel; private float mIconScale; - BluetoothBatteryDrawable(int level) { - this(level, 1 /* iconScale */); + BluetoothBatteryTileIcon(int batteryLevel, float iconScale) { + mBatteryLevel = batteryLevel; + mIconScale = iconScale; } - BluetoothBatteryDrawable(int level, float iconScale) { - mLevel = level; - mIconScale = iconScale; + @Override + public Drawable getDrawable(Context context) { + // This method returns Pair<Drawable, String> while first value is the drawable + return BluetoothDeviceLayerDrawable.createLayerDrawable( + context, + R.drawable.ic_qs_bluetooth_connected, + mBatteryLevel, + mIconScale); + } + } + + + /** + * Bluetooth icon wrapper (when connected with no battery indicator) for Quick Settings. This is + * used instead of {@link com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon} in order to + * use a context that reflects dark/light theme attributes. + */ + private class BluetoothConnectedTileIcon extends Icon { + + BluetoothConnectedTileIcon() { + // Do nothing. Default constructor to limit visibility. } @Override public Drawable getDrawable(Context context) { - return createLayerDrawable(context, - R.drawable.ic_qs_bluetooth_connected, mLevel, mIconScale); + // This method returns Pair<Drawable, String> - the first value is the drawable. + return context.getDrawable(R.drawable.ic_qs_bluetooth_connected); } } @@ -302,15 +359,15 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { for (CachedBluetoothDevice device : devices) { if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue; final Item item = new Item(); - item.icon = R.drawable.ic_qs_bluetooth_on; + item.iconResId = R.drawable.ic_qs_bluetooth_on; item.line1 = device.getName(); item.tag = device; int state = device.getMaxConnectionState(); if (state == BluetoothProfile.STATE_CONNECTED) { - item.icon = R.drawable.ic_qs_bluetooth_connected; + item.iconResId = R.drawable.ic_qs_bluetooth_connected; int batteryLevel = device.getBatteryLevel(); if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { - item.iconDrawable = new BluetoothBatteryDrawable(batteryLevel); + item.icon = new BluetoothBatteryTileIcon(batteryLevel,1 /* iconScale */); item.line2 = mContext.getString( R.string.quick_settings_connected_battery_level, Utils.formatPercentage(batteryLevel)); @@ -321,7 +378,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { items.add(connectedDevices, item); connectedDevices++; } else if (state == BluetoothProfile.STATE_CONNECTING) { - item.icon = R.drawable.ic_qs_bluetooth_connecting; + item.iconResId = R.drawable.ic_qs_bluetooth_connecting; item.line2 = mContext.getString(R.string.quick_settings_connecting); items.add(connectedDevices, item); } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index fb396b9ddb07..678aa7116f73 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -19,26 +19,17 @@ package com.android.systemui.qs.tiles; import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; import android.app.Dialog; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.media.MediaRouter; -import android.os.UserHandle; import android.provider.Settings; import android.service.quicksettings.Tile; import android.util.Log; -import android.view.ContextThemeWrapper; import android.view.View; import android.view.View.OnAttachStateChangeListener; -import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.widget.Button; -import com.android.internal.app.MediaRouteChooserDialog; -import com.android.internal.app.MediaRouteControllerDialog; import com.android.internal.app.MediaRouteDialogPresenter; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -280,7 +271,7 @@ public class CastTile extends QSTileImpl<BooleanState> { for (CastDevice device : devices) { if (device.state == CastDevice.STATE_CONNECTED) { final Item item = new Item(); - item.icon = R.drawable.ic_qs_cast_on; + item.iconResId = R.drawable.ic_qs_cast_on; item.line1 = getDeviceName(device); item.line2 = mContext.getString(R.string.quick_settings_connected); item.tag = device; @@ -300,7 +291,7 @@ public class CastTile extends QSTileImpl<BooleanState> { final CastDevice device = mVisibleOrder.get(id); if (!devices.contains(device)) continue; final Item item = new Item(); - item.icon = R.drawable.ic_qs_cast_off; + item.iconResId = R.drawable.ic_qs_cast_off; item.line1 = getDeviceName(device); if (device.state == CastDevice.STATE_CONNECTING) { item.line2 = mContext.getString(R.string.quick_settings_connecting); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java index 0b0f58bc3ef1..0ce3e6aa9211 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java @@ -112,6 +112,9 @@ public class CellularTile extends QSTileImpl<SignalState> { @Override protected void handleClick() { + if (getState().state == Tile.STATE_UNAVAILABLE) { + return; + } if (mDataController.isMobileDataEnabled()) { if (mKeyguardMonitor.isSecure() && !mKeyguardMonitor.canSkipBouncer()) { mActivityStarter.postQSRunnableDismissingKeyguard(this::showDisableDialog); @@ -190,7 +193,7 @@ public class CellularTile extends QSTileImpl<SignalState> { } if (cb.airplaneModeEnabled | cb.noSim) { - state.state = Tile.STATE_INACTIVE; + state.state = Tile.STATE_UNAVAILABLE; } else { state.state = Tile.STATE_ACTIVE; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 9e265e2295c3..6205e9afcb03 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -19,6 +19,7 @@ package com.android.systemui.qs.tiles; import static android.provider.Settings.Global.ZEN_MODE_ALARMS; import static android.provider.Settings.Global.ZEN_MODE_OFF; +import android.app.Dialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -33,29 +34,29 @@ import android.provider.Settings.Global; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ZenRule; import android.service.quicksettings.Tile; -import android.util.Log; import android.util.Slog; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.ViewGroup; +import android.view.WindowManager; import android.widget.Switch; import android.widget.Toast; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settingslib.notification.EnableZenModeDialog; import com.android.systemui.Dependency; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.SysUIToast; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.DetailAdapter; -import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.ZenModeController; -import com.android.systemui.statusbar.policy.ZenModeController.Callback; import com.android.systemui.volume.ZenModePanel; /** Quick settings tile: Do not disturb **/ @@ -70,9 +71,6 @@ public class DndTile extends QSTileImpl<BooleanState> { private static final String ACTION_SET_VISIBLE = "com.android.systemui.dndtile.SET_VISIBLE"; private static final String EXTRA_VISIBLE = "visible"; - private static final QSTile.Icon TOTAL_SILENCE = - ResourceIcon.get(R.drawable.ic_qs_dnd_on_total_silence); - private final ZenModeController mController; private final DndDetailAdapter mDetailAdapter; @@ -131,15 +129,28 @@ public class DndTile extends QSTileImpl<BooleanState> { @Override protected void handleClick() { + // Zen is currently on if (mState.value) { mController.setZen(ZEN_MODE_OFF, null, TAG); } else { - int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, Global.ZEN_MODE_ALARMS); - mController.setZen(zen, null, TAG); + showDetail(true); } } @Override + public void showDetail(boolean show) { + mUiHandler.post(() -> { + Dialog mDialog = new EnableZenModeDialog(mContext).createDialog(); + mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + SystemUIDialog.setShowForAllUsers(mDialog, true); + SystemUIDialog.registerDismissListener(mDialog); + SystemUIDialog.setWindowOnTop(mDialog); + mUiHandler.post(() -> mDialog.show()); + mHost.collapsePanels(); + }); + } + + @Override protected void handleSecondaryClick() { if (mController.isVolumeRestricted()) { // Collapse the panels, so the user can see the toast. @@ -159,9 +170,7 @@ public class DndTile extends QSTileImpl<BooleanState> { showDetail(true); } }); - int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, - Global.ZEN_MODE_ALARMS); - mController.setZen(zen, null, TAG); + mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG); } else { showDetail(true); } @@ -182,29 +191,25 @@ public class DndTile extends QSTileImpl<BooleanState> { state.value = newValue; state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.slash.isSlashed = !state.value; + state.label = getTileLabel(); + state.secondaryLabel = ZenModeConfig.getDescription(mContext,zen != Global.ZEN_MODE_OFF, + mController.getConfig()); + state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on); checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME); switch (zen) { case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: - state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on); - state.label = mContext.getString(R.string.quick_settings_dnd_priority_label); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_dnd_priority_on); break; case Global.ZEN_MODE_NO_INTERRUPTIONS: - state.icon = TOTAL_SILENCE; - state.label = mContext.getString(R.string.quick_settings_dnd_none_label); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_dnd_none_on); break; case ZEN_MODE_ALARMS: - state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on); - state.label = mContext.getString(R.string.quick_settings_dnd_alarms_label); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_dnd_alarms_on); break; default: - state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on); - state.label = mContext.getString(R.string.quick_settings_dnd_label); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_dnd); break; @@ -313,9 +318,7 @@ public class DndTile extends QSTileImpl<BooleanState> { mController.setZen(ZEN_MODE_OFF, null, TAG); mAuto = false; } else { - int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, - ZEN_MODE_ALARMS); - mController.setZen(zen, null, TAG); + mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index 910b6b174062..080e320802e6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles; +import android.annotation.Nullable; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; @@ -37,7 +38,7 @@ import com.android.systemui.statusbar.policy.HotspotController; /** Quick settings tile: Hotspot **/ public class HotspotTile extends QSTileImpl<AirplaneBooleanState> { static final Intent TETHER_SETTINGS = new Intent().setComponent(new ComponentName( - "com.android.settings", "com.android.settings.TetherSettings")); + "com.android.settings", "com.android.settings.TetherSettings")); private final Icon mEnabledStatic = ResourceIcon.get(R.drawable.ic_hotspot); private final Icon mUnavailable = ResourceIcon.get(R.drawable.ic_hotspot_unavailable); @@ -112,17 +113,25 @@ public class HotspotTile extends QSTileImpl<AirplaneBooleanState> { if (state.slash == null) { state.slash = new SlashState(); } - state.label = mContext.getString(R.string.quick_settings_hotspot_label); + + final int numConnectedDevices; + final boolean isTransient = mController.isHotspotTransient(); checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_CONFIG_TETHERING); - if (arg instanceof Boolean) { - state.value = (boolean) arg; + if (arg instanceof CallbackInfo) { + CallbackInfo info = (CallbackInfo) arg; + state.value = info.enabled; + numConnectedDevices = info.numConnectedDevices; } else { state.value = mController.isHotspotEnabled(); + numConnectedDevices = mController.getNumConnectedDevices(); } + state.icon = mEnabledStatic; + state.label = mContext.getString(R.string.quick_settings_hotspot_label); + state.secondaryLabel = getSecondaryLabel(state.value, isTransient, numConnectedDevices); state.isAirplaneMode = mAirplaneMode.getValue() != 0; - state.isTransient = mController.isHotspotTransient(); + state.isTransient = isTransient; state.slash.isSlashed = !state.value && !state.isTransient; if (state.isTransient) { state.icon = ResourceIcon.get(R.drawable.ic_hotspot_transient_animation); @@ -133,6 +142,21 @@ public class HotspotTile extends QSTileImpl<AirplaneBooleanState> { : state.value || state.isTransient ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; } + @Nullable + private String getSecondaryLabel( + boolean enabled, boolean isTransient, int numConnectedDevices) { + if (isTransient) { + return mContext.getString(R.string.quick_settings_hotspot_secondary_label_transient); + } else if (numConnectedDevices > 0 && enabled) { + return mContext.getResources().getQuantityString( + R.plurals.quick_settings_hotspot_secondary_label_num_devices, + numConnectedDevices, + numConnectedDevices); + } + + return null; + } + @Override public int getMetricsCategory() { return MetricsEvent.QS_HOTSPOT; @@ -148,9 +172,30 @@ public class HotspotTile extends QSTileImpl<AirplaneBooleanState> { } private final class Callback implements HotspotController.Callback { + final CallbackInfo mCallbackInfo = new CallbackInfo(); + @Override - public void onHotspotChanged(boolean enabled) { - refreshState(enabled); + public void onHotspotChanged(boolean enabled, int numConnectedDevices) { + mCallbackInfo.enabled = enabled; + mCallbackInfo.numConnectedDevices = numConnectedDevices; + refreshState(mCallbackInfo); } - }; + } + + /** + * Holder for any hotspot state info that needs to passed from the callback to + * {@link #handleUpdateState(State, Object)}. + */ + protected static final class CallbackInfo { + boolean enabled; + int numConnectedDevices; + + @Override + public String toString() { + return new StringBuilder("CallbackInfo[") + .append("enabled=").append(enabled) + .append(",numConnectedDevices=").append(numConnectedDevices) + .append(']').toString(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java index c35f5917d6cc..d7f2a2642180 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java @@ -101,6 +101,9 @@ public class LocationTile extends QSTileImpl<BooleanState> { // state.visible = !(mKeyguard.isSecure() && mKeyguard.isShowing()); state.value = locationEnabled; checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_SHARE_LOCATION); + if (state.disabledByPolicy == false) { + checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_CONFIG_LOCATION); + } state.icon = mIcon; state.slash.isSlashed = !state.value; if (locationEnabled) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java index 4c2036141daf..3597929229e6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -16,34 +16,46 @@ package com.android.systemui.qs.tiles; +import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Intent; import android.provider.Settings; import android.service.quicksettings.Tile; +import android.support.annotation.StringRes; import android.widget.Switch; -import com.android.internal.app.NightDisplayController; -import com.android.internal.logging.MetricsLogger; +import com.android.internal.app.ColorDisplayController; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.R; import com.android.systemui.qs.QSHost; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.qs.tileimpl.QSTileImpl; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + public class NightDisplayTile extends QSTileImpl<BooleanState> - implements NightDisplayController.Callback { + implements ColorDisplayController.Callback { + + /** + * Pattern for {@link java.time.format.DateTimeFormatter} used to approximate the time to the + * nearest hour and add on the AM/PM indicator. + */ + private static final String HOUR_MINUTE_DATE_TIME_PATTERN = "h a"; + private static final String APPROXIMATE_HOUR_DATE_TIME_PATTERN = "h:m a"; + - private NightDisplayController mController; + private ColorDisplayController mController; private boolean mIsListening; public NightDisplayTile(QSHost host) { super(host); - mController = new NightDisplayController(mContext, ActivityManager.getCurrentUser()); + mController = new ColorDisplayController(mContext, ActivityManager.getCurrentUser()); } @Override public boolean isAvailable() { - return NightDisplayController.isAvailable(mContext); + return ColorDisplayController.isAvailable(mContext); } @Override @@ -65,7 +77,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> } // Make a new controller for the new user. - mController = new NightDisplayController(mContext, newUserId); + mController = new ColorDisplayController(mContext, newUserId); if (mIsListening) { mController.setListener(this); } @@ -75,13 +87,58 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> @Override protected void handleUpdateState(BooleanState state, Object arg) { - final boolean isActivated = mController.isActivated(); - state.value = isActivated; + state.value = mController.isActivated(); state.label = state.contentDescription = mContext.getString(R.string.quick_settings_night_display_label); state.icon = ResourceIcon.get(R.drawable.ic_qs_night_display_on); state.expandedAccessibilityClassName = Switch.class.getName(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; + state.secondaryLabel = getSecondaryLabel(state.value); + } + + /** + * Returns a {@link String} for the secondary label that reflects when the light will be turned + * on or off based on the current auto mode and night light activated status. + */ + @Nullable + private String getSecondaryLabel(boolean isNightLightActivated) { + switch(mController.getAutoMode()) { + case ColorDisplayController.AUTO_MODE_TWILIGHT: + // Auto mode related to sunrise & sunset. If the light is on, it's guaranteed to be + // turned off at sunrise. If it's off, it's guaranteed to be turned on at sunset. + return isNightLightActivated + ? mContext.getString( + R.string.quick_settings_night_secondary_label_until_sunrise) + : mContext.getString( + R.string.quick_settings_night_secondary_label_on_at_sunset); + + case ColorDisplayController.AUTO_MODE_CUSTOM: + // User-specified time, approximated to the nearest hour. + final @StringRes int toggleTimeStringRes; + final LocalTime toggleTime; + final DateTimeFormatter toggleTimeFormat; + + if (isNightLightActivated) { + toggleTime = mController.getCustomEndTime(); + toggleTimeStringRes = R.string.quick_settings_night_secondary_label_until; + } else { + toggleTime = mController.getCustomStartTime(); + toggleTimeStringRes = R.string.quick_settings_night_secondary_label_on_at; + } + + // Choose between just showing the hour or also showing the minutes (based on the + // user-selected toggle time). This helps reduce how much space the label takes. + toggleTimeFormat = DateTimeFormatter.ofPattern( + toggleTime.getMinute() == 0 + ? HOUR_MINUTE_DATE_TIME_PATTERN + : APPROXIMATE_HOUR_DATE_TIME_PATTERN); + + return mContext.getString(toggleTimeStringRes, toggleTime.format(toggleTimeFormat)); + + default: + // No secondary label when auto mode is disabled. + return null; + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java index 1e00894483ac..60422ee61aa5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java @@ -36,20 +36,8 @@ import com.android.systemui.statusbar.policy.RotationLockController.RotationLock /** Quick settings tile: Rotation **/ public class RotationLockTile extends QSTileImpl<BooleanState> { - private final AnimationIcon mPortraitToAuto - = new AnimationIcon(R.drawable.ic_portrait_to_auto_rotate_animation, - R.drawable.ic_portrait_from_auto_rotate); - private final AnimationIcon mAutoToPortrait - = new AnimationIcon(R.drawable.ic_portrait_from_auto_rotate_animation, - R.drawable.ic_portrait_to_auto_rotate); - - private final AnimationIcon mLandscapeToAuto - = new AnimationIcon(R.drawable.ic_landscape_to_auto_rotate_animation, - R.drawable.ic_landscape_from_auto_rotate); - private final AnimationIcon mAutoToLandscape - = new AnimationIcon(R.drawable.ic_landscape_from_auto_rotate_animation, - R.drawable.ic_landscape_to_auto_rotate); + private final Icon mIcon = ResourceIcon.get(R.drawable.ic_qs_auto_rotate); private final RotationLockController mController; public RotationLockTile(QSHost host) { @@ -93,19 +81,10 @@ public class RotationLockTile extends QSTileImpl<BooleanState> { protected void handleUpdateState(BooleanState state, Object arg) { if (mController == null) return; final boolean rotationLocked = mController.isRotationLocked(); - // TODO: Handle accessibility rotation lock and whatnot. state.value = !rotationLocked; - final boolean portrait = isCurrentOrientationLockPortrait(mController, mContext); - if (rotationLocked) { - final int label = portrait ? R.string.quick_settings_rotation_locked_portrait_label - : R.string.quick_settings_rotation_locked_landscape_label; - state.label = mContext.getString(label); - state.icon = portrait ? mAutoToPortrait : mAutoToLandscape; - } else { - state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label); - state.icon = portrait ? mPortraitToAuto : mLandscapeToAuto; - } + state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label); + state.icon = mIcon; state.contentDescription = getAccessibilityString(rotationLocked); state.expandedAccessibilityClassName = Switch.class.getName(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; @@ -134,18 +113,7 @@ public class RotationLockTile extends QSTileImpl<BooleanState> { * @param locked Whether or not rotation is locked. */ private String getAccessibilityString(boolean locked) { - if (locked) { - return mContext.getString(R.string.accessibility_quick_settings_rotation_value, - isCurrentOrientationLockPortrait(mController, mContext) - ? mContext.getString( - R.string.quick_settings_rotation_locked_portrait_label) - : mContext.getString( - R.string.quick_settings_rotation_locked_landscape_label)) - + "," + mContext.getString(R.string.accessibility_quick_settings_rotation); - - } else { - return mContext.getString(R.string.accessibility_quick_settings_rotation); - } + return mContext.getString(R.string.accessibility_quick_settings_rotation); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index 23702736b0db..977a725829bb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -402,7 +402,7 @@ public class WifiTile extends QSTileImpl<SignalState> { final AccessPoint ap = mAccessPoints[i]; final Item item = new Item(); item.tag = ap; - item.icon = mWifiController.getIcon(ap); + item.iconResId = mWifiController.getIcon(ap); item.line1 = ap.getSsid(); item.line2 = ap.isActive() ? ap.getSummary() : null; item.icon2 = ap.getSecurity() != AccessPoint.SECURITY_NONE diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java index 5f7d6fb41311..e098fd876735 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java @@ -83,7 +83,7 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements @Override public CharSequence getTileLabel() { - return mContext.getString(R.string.quick_settings_work_mode_label); + return mContext.getString(R.string.quick_settings_work_mode_on_label); } @Override @@ -98,16 +98,17 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements state.value = mProfileController.isWorkModeEnabled(); } - state.label = mContext.getString(R.string.quick_settings_work_mode_label); state.icon = mIcon; if (state.value) { state.slash.isSlashed = false; state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_work_mode_on); + state.label = mContext.getString(R.string.quick_settings_work_mode_on_label); } else { state.slash.isSlashed = true; state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_work_mode_off); + state.label = mContext.getString(R.string.quick_settings_work_mode_off_label); } state.expandedAccessibilityClassName = Switch.class.getName(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; diff --git a/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java b/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java new file mode 100644 index 000000000000..046488679725 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java @@ -0,0 +1,57 @@ +/* + * 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.qs.touch; + +/** + * Utility methods for overscroll damping and related effect. + * + * Copied from packages/apps/Launcher3/src/com/android/launcher3/touch/OverScroll.java + */ +public class OverScroll { + + private static final float OVERSCROLL_DAMP_FACTOR = 0.07f; + + /** + * This curve determines how the effect of scrolling over the limits of the page diminishes + * as the user pulls further and further from the bounds + * + * @param f The percentage of how much the user has overscrolled. + * @return A transformed percentage based on the influence curve. + */ + private static float overScrollInfluenceCurve(float f) { + f -= 1.0f; + return f * f * f + 1.0f; + } + + /** + * @param amount The original amount overscrolled. + * @param max The maximum amount that the View can overscroll. + * @return The dampened overscroll amount. + */ + public static int dampedScroll(float amount, int max) { + if (Float.compare(amount, 0) == 0) return 0; + + float f = amount / max; + f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); + + // Clamp this factor, f, to -1 < f < 1 + if (Math.abs(f) >= 1) { + f /= Math.abs(f); + } + + return Math.round(OVERSCROLL_DAMP_FACTOR * f * max); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java b/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java new file mode 100644 index 000000000000..252205201e5d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java @@ -0,0 +1,356 @@ +/* + * 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.qs.touch; + +import static android.view.MotionEvent.INVALID_POINTER_ID; + +import android.content.Context; +import android.graphics.PointF; +import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; +import android.util.Log; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +/** + * One dimensional scroll/drag/swipe gesture detector. + * + * Definition of swipe is different from android system in that this detector handles + * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before + * swipe action happens + * + * Copied from packages/apps/Launcher3/src/com/android/launcher3/touch/SwipeDetector.java + */ +public class SwipeDetector { + + private static final boolean DBG = false; + private static final String TAG = "SwipeDetector"; + + private int mScrollConditions; + public static final int DIRECTION_POSITIVE = 1 << 0; + public static final int DIRECTION_NEGATIVE = 1 << 1; + public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE; + + private static final float ANIMATION_DURATION = 1200; + + protected int mActivePointerId = INVALID_POINTER_ID; + + /** + * The minimum release velocity in pixels per millisecond that triggers fling.. + */ + public static final float RELEASE_VELOCITY_PX_MS = 1.0f; + + /** + * The time constant used to calculate dampening in the low-pass filter of scroll velocity. + * Cutoff frequency is set at 10 Hz. + */ + public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10); + + /* Scroll state, this is set to true during dragging and animation. */ + private ScrollState mState = ScrollState.IDLE; + + enum ScrollState { + IDLE, + DRAGGING, // onDragStart, onDrag + SETTLING // onDragEnd + } + + public static abstract class Direction { + + abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint); + + /** + * Distance in pixels a touch can wander before we think the user is scrolling. + */ + abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos); + } + + public static final Direction VERTICAL = new Direction() { + + @Override + float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint) { + return ev.getY(pointerIndex) - refPoint.y; + } + + @Override + float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) { + return Math.abs(ev.getX(pointerIndex) - downPos.x); + } + }; + + public static final Direction HORIZONTAL = new Direction() { + + @Override + float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint) { + return ev.getX(pointerIndex) - refPoint.x; + } + + @Override + float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) { + return Math.abs(ev.getY(pointerIndex) - downPos.y); + } + }; + + //------------------- ScrollState transition diagram ----------------------------------- + // + // IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING + // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING + // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING + // SETTLING -> (View settled) -> IDLE + + private void setState(ScrollState newState) { + if (DBG) { + Log.d(TAG, "setState:" + mState + "->" + newState); + } + // onDragStart and onDragEnd is reported ONLY on state transition + if (newState == ScrollState.DRAGGING) { + initializeDragging(); + if (mState == ScrollState.IDLE) { + reportDragStart(false /* recatch */); + } else if (mState == ScrollState.SETTLING) { + reportDragStart(true /* recatch */); + } + } + if (newState == ScrollState.SETTLING) { + reportDragEnd(); + } + + mState = newState; + } + + public boolean isDraggingOrSettling() { + return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING; + } + + /** + * There's no touch and there's no animation. + */ + public boolean isIdleState() { + return mState == ScrollState.IDLE; + } + + public boolean isSettlingState() { + return mState == ScrollState.SETTLING; + } + + public boolean isDraggingState() { + return mState == ScrollState.DRAGGING; + } + + private final PointF mDownPos = new PointF(); + private final PointF mLastPos = new PointF(); + private final Direction mDir; + + private final float mTouchSlop; + + /* Client of this gesture detector can register a callback. */ + private final Listener mListener; + + private long mCurrentMillis; + + private float mVelocity; + private float mLastDisplacement; + private float mDisplacement; + + private float mSubtractDisplacement; + private boolean mIgnoreSlopWhenSettling; + + public interface Listener { + void onDragStart(boolean start); + + boolean onDrag(float displacement, float velocity); + + void onDragEnd(float velocity, boolean fling); + } + + public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) { + this(ViewConfiguration.get(context).getScaledTouchSlop(), l, dir); + } + + @VisibleForTesting + protected SwipeDetector(float touchSlope, @NonNull Listener l, @NonNull Direction dir) { + mTouchSlop = touchSlope; + mListener = l; + mDir = dir; + } + + public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) { + mScrollConditions = scrollDirectionFlags; + mIgnoreSlopWhenSettling = ignoreSlop; + } + + private boolean shouldScrollStart(MotionEvent ev, int pointerIndex) { + // reject cases where the angle or slop condition is not met. + if (Math.max(mDir.getActiveTouchSlop(ev, pointerIndex, mDownPos), mTouchSlop) + > Math.abs(mDisplacement)) { + return false; + } + + // Check if the client is interested in scroll in current direction. + if (((mScrollConditions & DIRECTION_NEGATIVE) > 0 && mDisplacement > 0) || + ((mScrollConditions & DIRECTION_POSITIVE) > 0 && mDisplacement < 0)) { + return true; + } + return false; + } + + public boolean onTouchEvent(MotionEvent ev) { + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mActivePointerId = ev.getPointerId(0); + mDownPos.set(ev.getX(), ev.getY()); + mLastPos.set(mDownPos); + mLastDisplacement = 0; + mDisplacement = 0; + mVelocity = 0; + + if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) { + setState(ScrollState.DRAGGING); + } + break; + //case MotionEvent.ACTION_POINTER_DOWN: + case MotionEvent.ACTION_POINTER_UP: + int ptrIdx = ev.getActionIndex(); + int ptrId = ev.getPointerId(ptrIdx); + if (ptrId == mActivePointerId) { + final int newPointerIdx = ptrIdx == 0 ? 1 : 0; + mDownPos.set( + ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x), + ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y)); + mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx)); + mActivePointerId = ev.getPointerId(newPointerIdx); + } + break; + case MotionEvent.ACTION_MOVE: + int pointerIndex = ev.findPointerIndex(mActivePointerId); + if (pointerIndex == INVALID_POINTER_ID) { + break; + } + mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos); + computeVelocity(mDir.getDisplacement(ev, pointerIndex, mLastPos), + ev.getEventTime()); + + // handle state and listener calls. + if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) { + setState(ScrollState.DRAGGING); + } + if (mState == ScrollState.DRAGGING) { + reportDragging(); + } + mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + // These are synthetic events and there is no need to update internal values. + if (mState == ScrollState.DRAGGING) { + setState(ScrollState.SETTLING); + } + break; + default: + break; + } + return true; + } + + public void finishedScrolling() { + setState(ScrollState.IDLE); + } + + private boolean reportDragStart(boolean recatch) { + mListener.onDragStart(!recatch); + if (DBG) { + Log.d(TAG, "onDragStart recatch:" + recatch); + } + return true; + } + + private void initializeDragging() { + if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) { + mSubtractDisplacement = 0; + } + if (mDisplacement > 0) { + mSubtractDisplacement = mTouchSlop; + } else { + mSubtractDisplacement = -mTouchSlop; + } + } + + private boolean reportDragging() { + if (mDisplacement != mLastDisplacement) { + if (DBG) { + Log.d(TAG, String.format("onDrag disp=%.1f, velocity=%.1f", + mDisplacement, mVelocity)); + } + + mLastDisplacement = mDisplacement; + return mListener.onDrag(mDisplacement - mSubtractDisplacement, mVelocity); + } + return true; + } + + private void reportDragEnd() { + if (DBG) { + Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f", + mDisplacement, mVelocity)); + } + mListener.onDragEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS); + + } + + /** + * Computes the damped velocity. + */ + public float computeVelocity(float delta, long currentMillis) { + long previousMillis = mCurrentMillis; + mCurrentMillis = currentMillis; + + float deltaTimeMillis = mCurrentMillis - previousMillis; + float velocity = (deltaTimeMillis > 0) ? (delta / deltaTimeMillis) : 0; + if (Math.abs(mVelocity) < 0.001f) { + mVelocity = velocity; + } else { + float alpha = computeDampeningFactor(deltaTimeMillis); + mVelocity = interpolate(mVelocity, velocity, alpha); + } + return mVelocity; + } + + /** + * Returns a time-dependent dampening factor using delta time. + */ + private static float computeDampeningFactor(float deltaTime) { + return deltaTime / (SCROLL_VELOCITY_DAMPENING_RC + deltaTime); + } + + /** + * Returns the linear interpolation between two values + */ + private static float interpolate(float from, float to, float alpha) { + return (1.0f - alpha) * from + alpha * to; + } + + public static long calculateDuration(float velocity, float progressNeeded) { + // TODO: make these values constants after tuning. + float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity)); + float travelDistance = Math.max(0.2f, progressNeeded); + long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance); + if (DBG) { + Log.d(TAG, String.format("calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded)); + } + return duration; + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl index 9214eef61df5..fc1831d55c9d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl +++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl @@ -27,11 +27,11 @@ oneway interface IRecentsNonSystemUserCallbacks { void preloadRecents(); void cancelPreloadingRecents(); void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents, boolean animate, - boolean reloadTasks, boolean fromHome, int recentsGrowTarget); + int recentsGrowTarget); void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey); void toggleRecents(int recentsGrowTarget); void onConfigurationChanged(); - void dockTopTask(int topTaskId, int dragMode, int stackCreateMode, + void splitPrimaryTask(int topTaskId, int dragMode, int stackCreateMode, in Rect initialBounds); void onDraggingInRecents(float distanceFromTop); void onDraggingInRecentsEnded(float velocity); diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl index cc7798e8721b..58d8d8fd600a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl +++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl @@ -31,5 +31,6 @@ oneway interface IRecentsSystemUserCallbacks { void sendRecentsDrawnEvent(); void sendDockingTopTaskEvent(int dragMode, in Rect initialRect); void sendLaunchRecentsEvent(); + void sendDockedFirstAnimationFrameEvent(); void setWaitingForTransitionStartEvent(boolean waitingForTransitionStart); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 406bcac08daf..409c753c147c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -16,6 +16,9 @@ package com.android.systemui.recents; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS; import android.app.ActivityManager; @@ -26,14 +29,13 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManager; -import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; -import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; import android.util.EventLog; @@ -50,6 +52,7 @@ import com.android.systemui.RecentsComponent; import com.android.systemui.SystemUI; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.ConfigurationChangedEvent; +import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent; import com.android.systemui.recents.events.activity.DockedTopTaskEvent; import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent; import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; @@ -59,7 +62,8 @@ import com.android.systemui.recents.events.component.SetWaitingForTransitionStar import com.android.systemui.recents.events.component.ShowUserToastEvent; import com.android.systemui.recents.events.ui.RecentsDrawnEvent; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.model.RecentsTaskLoader; +import com.android.systemui.shared.recents.model.RecentsTaskLoader; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; @@ -78,23 +82,15 @@ public class Recents extends SystemUI implements RecentsComponent, CommandQueue.Callbacks { private final static String TAG = "Recents"; - private final static boolean DEBUG = false; public final static int EVENT_BUS_PRIORITY = 1; public final static int BIND_TO_SYSTEM_USER_RETRY_DELAY = 5000; - public final static int RECENTS_GROW_TARGET_INVALID = -1; public final static Set<String> RECENTS_ACTIVITIES = new HashSet<>(); static { RECENTS_ACTIVITIES.add(RecentsImpl.RECENTS_ACTIVITY); } - // Purely for experimentation - private final static String RECENTS_OVERRIDE_SYSPROP_KEY = "persist.recents_override_pkg"; - private final static String ACTION_SHOW_RECENTS = "com.android.systemui.recents.ACTION_SHOW"; - private final static String ACTION_HIDE_RECENTS = "com.android.systemui.recents.ACTION_HIDE"; - private final static String ACTION_TOGGLE_RECENTS = "com.android.systemui.recents.ACTION_TOGGLE"; - private static final String COUNTER_WINDOW_SUPPORTED = "window_enter_supported"; private static final String COUNTER_WINDOW_UNSUPPORTED = "window_enter_unsupported"; private static final String COUNTER_WINDOW_INCOMPATIBLE = "window_enter_incompatible"; @@ -104,11 +100,6 @@ public class Recents extends SystemUI private static RecentsTaskLoader sTaskLoader; private static RecentsConfiguration sConfiguration; - // For experiments only, allows another package to handle recents if it is defined in the system - // properties. This is limited to show/toggle/hide, and does not tie into the ActivityManager, - // and does not reside in the home stack. - private String mOverrideRecentsPackageName; - private Handler mHandler; private RecentsImpl mImpl; private int mDraggingInRecentsCurrentUser; @@ -201,21 +192,23 @@ public class Recents extends SystemUI @Override public void start() { - sDebugFlags = new RecentsDebugFlags(mContext); + final Resources res = mContext.getResources(); + final int defaultTaskBarBackgroundColor = + mContext.getColor(R.color.recents_task_bar_default_background_color); + final int defaultTaskViewBackgroundColor = + mContext.getColor(R.color.recents_task_view_default_background_color); + sDebugFlags = new RecentsDebugFlags(); sSystemServicesProxy = SystemServicesProxy.getInstance(mContext); sConfiguration = new RecentsConfiguration(mContext); - sTaskLoader = new RecentsTaskLoader(mContext); + sTaskLoader = new RecentsTaskLoader(mContext, + // TODO: Once we start building the AAR, move these into the loader + res.getInteger(R.integer.config_recents_max_thumbnail_count), + res.getInteger(R.integer.config_recents_max_icon_count), + res.getInteger(R.integer.recents_svelte_level)); + sTaskLoader.setDefaultColors(defaultTaskBarBackgroundColor, defaultTaskViewBackgroundColor); mHandler = new Handler(); mImpl = new RecentsImpl(mContext); - // Check if there is a recents override package - if (Build.IS_USERDEBUG || Build.IS_ENG) { - String cnStr = SystemProperties.get(RECENTS_OVERRIDE_SYSPROP_KEY); - if (!cnStr.isEmpty()) { - mOverrideRecentsPackageName = cnStr; - } - } - // Register with the event bus EventBus.getDefault().register(this, EVENT_BUS_PRIORITY); EventBus.getDefault().register(sSystemServicesProxy, EVENT_BUS_PRIORITY); @@ -247,27 +240,19 @@ public class Recents extends SystemUI * Shows the Recents. */ @Override - public void showRecentApps(boolean triggeredFromAltTab, boolean fromHome) { + public void showRecentApps(boolean triggeredFromAltTab) { // Ensure the device has been provisioned before allowing the user to interact with // recents if (!isUserSetup()) { return; } - if (proxyToOverridePackage(ACTION_SHOW_RECENTS)) { - return; - } - try { - ActivityManager.getService().closeSystemDialogs(SYSTEM_DIALOG_REASON_RECENT_APPS); - } catch (RemoteException e) { - } - + ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS); int recentsGrowTarget = getComponent(Divider.class).getView().growsRecents(); - int currentUser = sSystemServicesProxy.getCurrentUser(); if (sSystemServicesProxy.isSystemUser(currentUser)) { mImpl.showRecents(triggeredFromAltTab, false /* draggingInRecents */, - true /* animate */, false /* reloadTasks */, fromHome, recentsGrowTarget); + true /* animate */, recentsGrowTarget); } else { if (mSystemToUserCallbacks != null) { IRecentsNonSystemUserCallbacks callbacks = @@ -275,8 +260,7 @@ public class Recents extends SystemUI if (callbacks != null) { try { callbacks.showRecents(triggeredFromAltTab, false /* draggingInRecents */, - true /* animate */, false /* reloadTasks */, fromHome, - recentsGrowTarget); + true /* animate */, recentsGrowTarget); } catch (RemoteException e) { Log.e(TAG, "Callback failed", e); } @@ -298,10 +282,6 @@ public class Recents extends SystemUI return; } - if (proxyToOverridePackage(ACTION_HIDE_RECENTS)) { - return; - } - int currentUser = sSystemServicesProxy.getCurrentUser(); if (sSystemServicesProxy.isSystemUser(currentUser)) { mImpl.hideRecents(triggeredFromAltTab, triggeredFromHomeKey); @@ -333,12 +313,7 @@ public class Recents extends SystemUI return; } - if (proxyToOverridePackage(ACTION_TOGGLE_RECENTS)) { - return; - } - int growTarget = getComponent(Divider.class).getView().growsRecents(); - int currentUser = sSystemServicesProxy.getCurrentUser(); if (sSystemServicesProxy.isSystemUser(currentUser)) { mImpl.toggleRecents(growTarget); @@ -419,7 +394,7 @@ public class Recents extends SystemUI } @Override - public boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds, + public boolean splitPrimaryTask(int dragMode, int stackCreateMode, Rect initialBounds, int metricsDockAction) { // Ensure the device has been provisioned before allowing the user to interact with // recents @@ -435,11 +410,14 @@ public class Recents extends SystemUI } int currentUser = sSystemServicesProxy.getCurrentUser(); - SystemServicesProxy ssp = Recents.getSystemServices(); - ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask(); - boolean screenPinningActive = ssp.isScreenPinningActive(); - boolean isRunningTaskInHomeOrRecentsStack = runningTask != null && - ActivityManager.StackId.isHomeOrRecentsStack(runningTask.stackId); + ActivityManager.RunningTaskInfo runningTask = + ActivityManagerWrapper.getInstance().getRunningTask(); + final int activityType = runningTask != null + ? runningTask.configuration.windowConfiguration.getActivityType() + : ACTIVITY_TYPE_UNDEFINED; + boolean screenPinningActive = ActivityManagerWrapper.getInstance().isLockToAppActive(); + boolean isRunningTaskInHomeOrRecentsStack = + activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS; if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) { logDockAttempt(mContext, runningTask.topActivity, runningTask.resizeMode); if (runningTask.supportsSplitScreenMultiWindow) { @@ -448,15 +426,16 @@ public class Recents extends SystemUI runningTask.topActivity.flattenToShortString()); } if (sSystemServicesProxy.isSystemUser(currentUser)) { - mImpl.dockTopTask(runningTask.id, dragMode, stackCreateMode, initialBounds); + mImpl.splitPrimaryTask(runningTask.id, dragMode, stackCreateMode, + initialBounds); } else { if (mSystemToUserCallbacks != null) { IRecentsNonSystemUserCallbacks callbacks = mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser); if (callbacks != null) { try { - callbacks.dockTopTask(runningTask.id, dragMode, stackCreateMode, - initialBounds); + callbacks.splitPrimaryTask(runningTask.id, dragMode, + stackCreateMode, initialBounds); } catch (RemoteException e) { Log.e(TAG, "Callback failed", e); } @@ -628,6 +607,23 @@ public class Recents extends SystemUI } } + public final void onBusEvent(DockedFirstAnimationFrameEvent event) { + SystemServicesProxy ssp = Recents.getSystemServices(); + int processUser = ssp.getProcessUser(); + if (!ssp.isSystemUser(processUser)) { + postToSystemUser(new Runnable() { + @Override + public void run() { + try { + mUserToSystemCallbacks.sendDockedFirstAnimationFrameEvent(); + } catch (RemoteException e) { + Log.e(TAG, "Callback failed", e); + } + } + }); + } + } + /** * Handle screen pinning request. */ @@ -814,21 +810,6 @@ public class Recents extends SystemUI (Settings.Secure.getInt(cr, Settings.Secure.USER_SETUP_COMPLETE, 0) != 0); } - /** - * Attempts to proxy the following action to the override recents package. - * @return whether the proxying was successful - */ - private boolean proxyToOverridePackage(String action) { - if (mOverrideRecentsPackageName != null) { - Intent intent = new Intent(action); - intent.setPackage(mOverrideRecentsPackageName); - intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - mContext.sendBroadcast(intent); - return true; - } - return false; - } - @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Recents"); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index 86b77900fdbc..b0a2fadf1f84 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -16,8 +16,9 @@ package com.android.systemui.recents; +import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY; + import android.app.Activity; -import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.TaskStackBuilder; import android.app.WallpaperManager; @@ -29,10 +30,10 @@ import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; -import android.provider.Settings.Secure; import android.util.Log; import android.view.KeyEvent; import android.view.View; @@ -42,9 +43,10 @@ import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import com.android.internal.colorextraction.ColorExtractor; +import com.android.internal.content.PackageMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.keyguard.LatencyTracker; +import com.android.internal.util.LatencyTracker; import com.android.systemui.DejankUtils; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; @@ -53,7 +55,6 @@ import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent; import com.android.systemui.recents.events.activity.ConfigurationChangedEvent; -import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent; import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent; import com.android.systemui.recents.events.activity.DockedTopTaskEvent; @@ -61,10 +62,10 @@ import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationC import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent; import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent; import com.android.systemui.recents.events.activity.HideRecentsEvent; -import com.android.systemui.recents.events.activity.IterateRecentsEvent; import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent; import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent; import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent; +import com.android.systemui.recents.events.activity.PackagesChangedEvent; import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; import com.android.systemui.recents.events.activity.ToggleRecentsEvent; import com.android.systemui.recents.events.component.ActivityUnpinnedEvent; @@ -79,28 +80,24 @@ import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent; import com.android.systemui.recents.events.ui.StackViewScrolledEvent; import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; -import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent; import com.android.systemui.recents.events.ui.UserInteractionEvent; import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent; import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent; import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent; import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent; import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction; -import com.android.systemui.recents.misc.DozeTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.Utilities; -import com.android.systemui.recents.model.RecentsPackageMonitor; -import com.android.systemui.recents.model.RecentsTaskLoadPlan; -import com.android.systemui.recents.model.RecentsTaskLoader; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan; +import com.android.systemui.shared.recents.model.RecentsTaskLoader; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.TaskStack; import com.android.systemui.recents.views.RecentsView; import com.android.systemui.recents.views.SystemBarScrimViews; -import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.shared.system.ActivityManagerWrapper; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.List; /** * The main Recents activity that is started from RecentsComponent. @@ -114,7 +111,23 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1; public final static int INCOMPATIBLE_APP_ALPHA_DURATION = 150; - private RecentsPackageMonitor mPackageMonitor; + private PackageMonitor mPackageMonitor = new PackageMonitor() { + @Override + public void onPackageRemoved(String packageName, int uid) { + RecentsActivity.this.onPackageChanged(packageName, getChangingUserId()); + } + + @Override + public boolean onPackageChanged(String packageName, int uid, String[] components) { + RecentsActivity.this.onPackageChanged(packageName, getChangingUserId()); + return true; + } + + @Override + public void onPackageModified(String packageName) { + RecentsActivity.this.onPackageChanged(packageName, getChangingUserId()); + } + }; private Handler mHandler = new Handler(); private long mLastTabKeyEventTime; private boolean mFinishedOnStartup; @@ -133,7 +146,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD // The trigger to automatically launch the current task private int mFocusTimerDuration; - private DozeTrigger mIterateTrigger; private final UserInteractionEvent mUserInteractionEvent = new UserInteractionEvent(); // Theme and colors @@ -188,41 +200,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { // When switching users, dismiss Recents to Home similar to screen off finish(); - } else if (action.equals(Intent.ACTION_TIME_CHANGED)) { - // If the time shifts but the currentTime >= lastStackActiveTime, then that boundary - // is still valid. Otherwise, we need to reset the lastStackactiveTime to the - // currentTime and remove the old tasks in between which would not be previously - // visible, but currently would be in the new currentTime - int currentUser = SystemServicesProxy.getInstance(RecentsActivity.this) - .getCurrentUser(); - long oldLastStackActiveTime = Settings.Secure.getLongForUser(getContentResolver(), - Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, -1, currentUser); - if (oldLastStackActiveTime != -1) { - long currentTime = System.currentTimeMillis(); - if (currentTime < oldLastStackActiveTime) { - // We are only removing tasks that are between the new current time - // and the old last stack active time, they were not visible and in the - // TaskStack so we don't need to remove any associated TaskViews but we do - // need to load the task id's from the system - RecentsTaskLoader loader = Recents.getTaskLoader(); - RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(ctx); - loader.preloadRawTasks(loadPlan, false /* includeFrontMostExcludedTask */); - List<ActivityManager.RecentTaskInfo> tasks = loadPlan.getRawTasks(); - for (int i = tasks.size() - 1; i >= 0; i--) { - ActivityManager.RecentTaskInfo task = tasks.get(i); - if (currentTime <= task.lastActiveTime && task.lastActiveTime < - oldLastStackActiveTime) { - Recents.getSystemServices().removeTask(task.persistentId); - } - } - Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync( - currentTime, currentUser); - - // Clear the last PiP task time, it's an edge case and we'd rather it - // not relaunch the PiP task if the user double taps - RecentsImpl.clearLastPipTime(); - } - } } } }; @@ -305,8 +282,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD new DismissRecentsToHomeAnimationStarted(animateTaskViews); dismissEvent.addPostAnimationCallback(new LaunchHomeRunnable(mHomeIntent, overrideAnimation)); - Recents.getSystemServices().sendCloseSystemWindows( - StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY); + ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); EventBus.getDefault().send(dismissEvent); } @@ -340,8 +316,8 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD EventBus.getDefault().register(this, EVENT_BUS_PRIORITY); // Initialize the package monitor - mPackageMonitor = new RecentsPackageMonitor(); - mPackageMonitor.register(this); + mPackageMonitor.register(this, Looper.getMainLooper(), UserHandle.ALL, + true /* externalStorage */); // Select theme based on wallpaper colors mColorExtractor = Dependency.get(SysuiColorExtractor.class); @@ -363,13 +339,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD } mLastConfig = new Configuration(Utilities.getAppConfiguration(this)); - mFocusTimerDuration = getResources().getInteger(R.integer.recents_auto_advance_duration); - mIterateTrigger = new DozeTrigger(mFocusTimerDuration, new Runnable() { - @Override - public void run() { - dismissRecentsToFocusedTask(MetricsEvent.OVERVIEW_SELECT_TIMEOUT); - } - }); // Set the window background mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode()); @@ -383,20 +352,19 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD // Register the broadcast receiver to handle messages when the screen is turned off IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_USER_SWITCHED); registerReceiver(mSystemBroadcastReceiver, filter); getWindow().addPrivateFlags(LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION); - - // Reload the stack view - reloadStackView(); } @Override protected void onStart() { super.onStart(); + // Reload the stack view whenever we are made visible again + reloadStackView(); + // Notify that recents is now visible EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true)); MetricsLogger.visible(this, MetricsEvent.OVERVIEW_ACTIVITY); @@ -443,14 +411,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD } } - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - - // Reload the stack view - reloadStackView(); - } - /** * Reloads the stack views upon launching Recents. */ @@ -460,22 +420,21 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD RecentsTaskLoader loader = Recents.getTaskLoader(); RecentsTaskLoadPlan loadPlan = RecentsImpl.consumeInstanceLoadPlan(); if (loadPlan == null) { - loadPlan = loader.createLoadPlan(this); + loadPlan = new RecentsTaskLoadPlan(this); } // Start loading tasks according to the load plan RecentsConfiguration config = Recents.getConfiguration(); RecentsActivityLaunchState launchState = config.getLaunchState(); if (!loadPlan.hasTasks()) { - loader.preloadTasks(loadPlan, launchState.launchedToTaskId, - !launchState.launchedFromHome && !launchState.launchedViaDockGesture); + loader.preloadTasks(loadPlan, launchState.launchedToTaskId); } RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options(); loadOpts.runningTaskId = launchState.launchedToTaskId; loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks; loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails; - loader.loadTasks(this, loadPlan, loadOpts); + loader.loadTasks(loadPlan, loadOpts); TaskStack stack = loadPlan.getTaskStack(); mRecentsView.onReload(stack, mIsVisible); @@ -502,7 +461,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD if (launchState.launchedFromApp) { Task launchTarget = stack.getLaunchTarget(); int launchTaskIndexInStack = launchTarget != null - ? stack.indexOfStackTask(launchTarget) + ? stack.indexOfTask(launchTarget) : 0; MetricsLogger.count(this, "overview_source_app", 1); // If from an app, track the stack index of the app in the stack (for affiliated tasks) @@ -540,7 +499,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD super.onPause(); mIgnoreAltTabRelease = false; - mIterateTrigger.stopDozing(); } @Override @@ -549,7 +507,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD // Notify of the config change Configuration newDeviceConfiguration = Utilities.getAppConfiguration(this); - int numStackTasks = mRecentsView.getStack().getStackTaskCount(); + int numStackTasks = mRecentsView.getStack().getTaskCount(); EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */, mLastConfig.orientation != newDeviceConfiguration.orientation, mLastConfig.densityDpi != newDeviceConfiguration.densityDpi, numStackTasks > 0)); @@ -564,7 +522,11 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD // Set the window background mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode); - reloadTaskStack(isInMultiWindowMode, true /* sendConfigChangedEvent */); + // Reload the task stack view if we are still visible to pick up the change in tasks that + // result from entering/exiting multi-window + if (mIsVisible) { + reloadTaskStack(isInMultiWindowMode, true /* sendConfigChangedEvent */); + } } @Override @@ -643,8 +605,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD if (backward) { EventBus.getDefault().send(new FocusPreviousTaskViewEvent()); } else { - EventBus.getDefault().send( - new FocusNextTaskViewEvent(0 /* timerIndicatorDuration */)); + EventBus.getDefault().send(new FocusNextTaskViewEvent()); } mLastTabKeyEventTime = SystemClock.elapsedRealtime(); @@ -702,38 +663,10 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD } } - public final void onBusEvent(IterateRecentsEvent event) { - final RecentsDebugFlags debugFlags = Recents.getDebugFlags(); - - // Start dozing after the recents button is clicked - int timerIndicatorDuration = 0; - if (debugFlags.isFastToggleRecentsEnabled()) { - timerIndicatorDuration = getResources().getInteger( - R.integer.recents_subsequent_auto_advance_duration); - - mIterateTrigger.setDozeDuration(timerIndicatorDuration); - if (!mIterateTrigger.isDozing()) { - mIterateTrigger.startDozing(); - } else { - mIterateTrigger.poke(); - } - } - - // Focus the next task - EventBus.getDefault().send(new FocusNextTaskViewEvent(timerIndicatorDuration)); - - MetricsLogger.action(this, MetricsEvent.ACTION_OVERVIEW_PAGE); - } - public final void onBusEvent(RecentsActivityStartingEvent event) { mRecentsStartRequested = true; } - public final void onBusEvent(UserInteractionEvent event) { - // Stop the fast-toggle dozer - mIterateTrigger.stopDozing(); - } - public final void onBusEvent(HideRecentsEvent event) { if (event.triggeredFromAltTab) { // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app @@ -751,15 +684,11 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD } public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) { - EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(true)); mRecentsView.getViewTreeObserver().addOnPreDrawListener(this); mRecentsView.invalidate(); } public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) { - if (mRecentsView.isLastTaskLaunchedFreeform()) { - EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(false)); - } mRecentsView.getViewTreeObserver().addOnPreDrawListener(this); mRecentsView.invalidate(); } @@ -774,9 +703,8 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD int launchToTaskId = launchState.launchedToTaskId; if (launchToTaskId != -1 && (event.launchTask == null || launchToTaskId != event.launchTask.key.id)) { - SystemServicesProxy ssp = Recents.getSystemServices(); - ssp.cancelWindowTransition(launchState.launchedToTaskId); - ssp.cancelThumbnailTransition(getTaskId()); + ActivityManagerWrapper am = ActivityManagerWrapper.getInstance(); + am.cancelWindowTransition(launchState.launchedToTaskId); } } @@ -823,8 +751,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD loader.deleteTaskData(event.task, false); // Remove the task from activity manager - SystemServicesProxy ssp = Recents.getSystemServices(); - ssp.removeTask(event.task.key.id); + ActivityManagerWrapper.getInstance().removeTask(event.task.key.id); } public final void onBusEvent(TaskViewDismissedEvent event) { @@ -859,11 +786,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD MetricsLogger.count(this, "overview_screen_pinned", 1); } - public final void onBusEvent(DebugFlagsChangedEvent event) { - // Just finish recents so that we can reload the flags anew on the next instantiation - finish(); - } - public final void onBusEvent(StackViewScrolledEvent event) { // Once the user has scrolled while holding alt-tab, then we should ignore the release of // the key @@ -888,17 +810,16 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD RecentsConfiguration config = Recents.getConfiguration(); RecentsActivityLaunchState launchState = config.getLaunchState(); RecentsTaskLoader loader = Recents.getTaskLoader(); - RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this); - loader.preloadTasks(loadPlan, -1 /* runningTaskId */, - false /* includeFrontMostExcludedTask */); + RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(this); + loader.preloadTasks(loadPlan, -1 /* runningTaskId */); RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options(); loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks; loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails; - loader.loadTasks(this, loadPlan, loadOpts); + loader.loadTasks(loadPlan, loadOpts); TaskStack stack = loadPlan.getTaskStack(); - int numStackTasks = stack.getStackTaskCount(); + int numStackTasks = stack.getTaskCount(); boolean showDeferredAnimation = numStackTasks > 0; if (sendConfigChangedEvent) { @@ -924,6 +845,11 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD return true; } + public void onPackageChanged(String packageName, int userId) { + Recents.getTaskLoader().onPackageChanged(packageName); + EventBus.getDefault().send(new PackagesChangedEvent(packageName, userId)); + } + @Override public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { super.dump(prefix, fd, writer, args); @@ -931,13 +857,9 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD Recents.getTaskLoader().dump(prefix, writer); String id = Integer.toHexString(System.identityHashCode(this)); - long lastStackActiveTime = Settings.Secure.getLongForUser(getContentResolver(), - Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, -1, - SystemServicesProxy.getInstance(this).getCurrentUser()); writer.print(prefix); writer.print(TAG); writer.print(" visible="); writer.print(mIsVisible ? "Y" : "N"); - writer.print(" lastStackTaskActiveTime="); writer.print(lastStackActiveTime); writer.print(" currentTime="); writer.print(System.currentTimeMillis()); writer.print(" [0x"); writer.print(id); writer.print("]"); writer.println(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java index 5b8ed94d5df0..14fda952b7ac 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java @@ -33,7 +33,6 @@ public class RecentsActivityLaunchState { public boolean launchedFromPipApp; // Set if the next activity that quick-switch will launch is the PiP activity public boolean launchedWithNextPipApp; - public boolean launchedFromBlacklistedApp; public boolean launchedFromHome; public boolean launchedViaDragGesture; public boolean launchedViaDockGesture; @@ -44,7 +43,6 @@ public class RecentsActivityLaunchState { public void reset() { launchedFromHome = false; launchedFromApp = false; - launchedFromBlacklistedApp = false; launchedFromPipApp = false; launchedWithNextPipApp = false; launchedToTaskId = -1; @@ -52,42 +50,4 @@ public class RecentsActivityLaunchState { launchedViaDragGesture = false; launchedViaDockGesture = false; } - - /** - * Returns the task to focus given the current launch state. - */ - public int getInitialFocusTaskIndex(int numTasks, boolean useGridLayout) { - RecentsDebugFlags debugFlags = Recents.getDebugFlags(); - RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); - if (launchedFromApp) { - if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) { - // If fast toggling, focus the front most task so that the next tap will launch the - // task - return numTasks - 1; - } - - if (launchState.launchedFromBlacklistedApp) { - // If we are launching from a blacklisted app, focus the front most task so that the - // next tap will launch the task - return numTasks - 1; - } - - if (useGridLayout) { - // If coming from another app to the grid layout, focus the front most task - return numTasks - 1; - } - - // If coming from another app, focus the next task - return Math.max(0, numTasks - 2); - } else { - if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) { - // If fast toggling, defer focusing until the next tap (which will automatically - // focus the front most task) - return -1; - } - - // If coming from home, focus the front most task - return numTasks - 1; - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java index 5dc6f31cae9a..68df1d5bd322 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java @@ -20,31 +20,31 @@ import android.app.ActivityManager; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Rect; import android.os.SystemProperties; import com.android.systemui.R; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.recents.views.DockState; +import com.android.systemui.shared.recents.model.TaskStack; /** * Represents the dock regions for each orientation. */ class DockRegion { - public static TaskStack.DockState[] PHONE_LANDSCAPE = { + public static DockState[] PHONE_LANDSCAPE = { // We only allow docking to the left in landscape for now on small devices - TaskStack.DockState.LEFT + DockState.LEFT }; - public static TaskStack.DockState[] PHONE_PORTRAIT = { + public static DockState[] PHONE_PORTRAIT = { // We only allow docking to the top for now on small devices - TaskStack.DockState.TOP + DockState.TOP }; - public static TaskStack.DockState[] TABLET_LANDSCAPE = { - TaskStack.DockState.LEFT, - TaskStack.DockState.RIGHT + public static DockState[] TABLET_LANDSCAPE = { + DockState.LEFT, + DockState.RIGHT }; - public static TaskStack.DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT; + public static DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT; } /** @@ -56,18 +56,6 @@ public class RecentsConfiguration { private static final int LARGE_SCREEN_MIN_DP = 600; private static final int XLARGE_SCREEN_MIN_DP = 720; - /** Levels of svelte in increasing severity/austerity. */ - // No svelting. - public static final int SVELTE_NONE = 0; - // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable - // caching thumbnails as you scroll. - public static final int SVELTE_LIMIT_CACHE = 1; - // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and - // evict all thumbnails when hidden. - public static final int SVELTE_DISABLE_CACHE = 2; - // Disable all thumbnail loading. - public static final int SVELTE_DISABLE_LOADING = 3; - // Launch states public RecentsActivityLaunchState mLaunchState = new RecentsActivityLaunchState(); @@ -125,7 +113,7 @@ public class RecentsConfiguration { * Returns the preferred dock states for the current orientation. * @return a list of dock states for device and its orientation */ - public TaskStack.DockState[] getDockStatesForCurrentOrientation() { + public DockState[] getDockStatesForCurrentOrientation() { boolean isLandscape = mAppContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; RecentsConfiguration config = Recents.getConfiguration(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java index 0262a098b8d2..19185939c553 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java @@ -16,75 +16,14 @@ package com.android.systemui.recents; -import android.content.Context; - -import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent; -import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.tuner.TunerService; - -/** - * Tunable debug flags - */ -public class RecentsDebugFlags implements TunerService.Tunable { +public class RecentsDebugFlags { public static class Static { // Enables debug drawing for the transition thumbnail public static final boolean EnableTransitionThumbnailDebugMode = false; - // This disables the bitmap and icon caches - public static final boolean DisableBackgroundCache = false; - // Enables the task affiliations - public static final boolean EnableAffiliatedTaskGroups = false; - // Enables the button above the stack - public static final boolean EnableStackActionButton = true; - // Overrides the Tuner flags and enables the timeout - private static final boolean EnableFastToggleTimeout = false; - // Overrides the Tuner flags and enables the paging via the Recents button - private static final boolean EnablePaging = false; + // Disables enter and exit transitions for other tasks for low ram devices public static final boolean DisableRecentsLowRamEnterExitAnimation = false; - // Enables us to create mock recents tasks - public static final boolean EnableMockTasks = false; - // Defines the number of mock recents packages to create - public static final int MockTasksPackageCount = 3; - // Defines the number of mock recents tasks to create - public static final int MockTaskCount = 100; - // Enables the simulated task affiliations - public static final boolean EnableMockTaskGroups = false; - // Defines the number of mock task affiliations per group - public static final int MockTaskGroupsTaskCount = 12; - } - - /** - * We read the prefs once when we start the activity, then update them as the tuner changes - * the flags. - */ - public RecentsDebugFlags(Context context) { - // Register all our flags, this will also call onTuningChanged() for each key, which will - // initialize the current state of each flag - } - - /** - * @return whether we are enabling fast toggling. - */ - public boolean isFastToggleRecentsEnabled() { - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.hasFreeformWorkspaceSupport() || ssp.isTouchExplorationEnabled()) { - return false; - } - return Static.EnableFastToggleTimeout; - } - - /** - * @return whether we are enabling paging. - */ - public boolean isPagingEnabled() { - return Static.EnablePaging; - } - - @Override - public void onTuningChanged(String key, String newValue) { - EventBus.getDefault().send(new DebugFlagsChangedEvent()); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index 5d5c4a0c69c6..3f6f30bba8c4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -16,30 +16,31 @@ package com.android.systemui.recents; -import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; -import static android.app.ActivityManager.StackId.INVALID_STACK_ID; -import static android.app.ActivityManager.StackId.isHomeOrRecentsStack; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.view.View.MeasureSpec; +import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS; + import android.app.ActivityManager; -import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityOptions; -import android.app.ActivityOptions.OnAnimationStartedListener; +import android.app.KeyguardManager; +import android.app.trust.TrustManager; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.res.Resources; -import android.graphics.GraphicBuffer; +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; import android.util.Log; import android.util.MutableBoolean; import android.util.Pair; -import android.view.AppTransitionAnimationSpec; import android.view.LayoutInflater; import android.view.ViewConfiguration; import android.view.WindowManager; @@ -56,7 +57,6 @@ import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.DockedTopTaskEvent; import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent; import com.android.systemui.recents.events.activity.HideRecentsEvent; -import com.android.systemui.recents.events.activity.IterateRecentsEvent; import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent; import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent; import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; @@ -72,28 +72,28 @@ import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent; import com.android.systemui.recents.misc.DozeTrigger; import com.android.systemui.recents.misc.ForegroundThread; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; -import com.android.systemui.recents.model.RecentsTaskLoadPlan; -import com.android.systemui.recents.model.RecentsTaskLoader; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.Task.TaskKey; -import com.android.systemui.recents.model.TaskGrouping; -import com.android.systemui.recents.model.TaskStack; -import com.android.systemui.recents.model.ThumbnailData; -import com.android.systemui.recents.views.RecentsTransitionHelper; -import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture; +import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; +import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan; +import com.android.systemui.shared.recents.model.RecentsTaskLoader; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.Task.TaskKey; +import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport; import com.android.systemui.recents.views.TaskStackView; -import com.android.systemui.recents.views.TaskStackViewScroller; import com.android.systemui.recents.views.TaskViewHeader; import com.android.systemui.recents.views.TaskViewTransform; import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm; +import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat; +import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; +import com.android.systemui.shared.recents.view.RecentsTransition; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.stackdivider.DividerView; -import com.android.systemui.statusbar.phone.NavigationBarGestureHelper; import com.android.systemui.statusbar.phone.StatusBar; import java.util.ArrayList; +import java.util.List; /** * An implementation of the Recents component for the current user. For secondary users, this can @@ -117,10 +117,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity"; /** - * An implementation of TaskStackListener, that allows us to listen for changes to the system + * An implementation of SysUiTaskStackChangeListener, that allows us to listen for changes to the system * task stacks and update recents accordingly. */ - class TaskStackListenerImpl extends TaskStackListener { + class TaskStackListenerImpl extends SysUiTaskStackChangeListener { @Override public void onTaskStackChangedBackground() { @@ -131,18 +131,18 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // Preloads the next task RecentsConfiguration config = Recents.getConfiguration(); - if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) { + if (config.svelteLevel == RecentsTaskLoader.SVELTE_NONE) { Rect windowRect = getWindowRect(null /* windowRectOverride */); if (windowRect.isEmpty()) { return; } // Load the next task only if we aren't svelte - SystemServicesProxy ssp = Recents.getSystemServices(); - ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask(); + ActivityManager.RunningTaskInfo runningTaskInfo = + ActivityManagerWrapper.getInstance().getRunningTask(); RecentsTaskLoader loader = Recents.getTaskLoader(); - RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); - loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */); + RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext); + loader.preloadTasks(plan, -1); TaskStack stack = plan.getTaskStack(); RecentsActivityLaunchState launchState = new RecentsActivityLaunchState(); RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); @@ -156,10 +156,11 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // Launched from app is always the worst case (in terms of how many // thumbnails/tasks visible) launchState.launchedFromApp = true; - mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState); + mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState, + -1 /* lastScrollPPresent */); VisibilityReport visibilityReport = mBackgroundLayoutAlgorithm.computeStackVisibilityReport( - stack.getStackTasks()); + stack.getTasks()); launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1; launchOpts.numVisibleTasks = visibilityReport.numVisibleTasks; @@ -168,12 +169,12 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener launchOpts.onlyLoadPausedActivities = true; launchOpts.loadThumbnails = true; } - loader.loadTasks(mContext, plan, launchOpts); + loader.loadTasks(plan, launchOpts); } } @Override - public void onActivityPinned(String packageName, int userId, int taskId) { + public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { // Check this is for the right user if (!checkCurrentUserId(mContext, false /* debug */)) { return; @@ -200,14 +201,13 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener } @Override - public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { + public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) { // Check this is for the right user if (!checkCurrentUserId(mContext, false /* debug */)) { return; } - EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId, - ThumbnailData.createFromTaskSnapshot(snapshot))); + EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId, snapshot)); } } @@ -221,14 +221,14 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // recents. In this case, we defer the toggle state until then and apply it immediately after. private static boolean mToggleFollowingTransitionStart = true; - private ActivityOptions.OnAnimationStartedListener mResetToggleFlagListener = - new OnAnimationStartedListener() { - @Override - public void onAnimationStarted() { - setWaitingForTransitionStart(false); - } - }; + private Runnable mResetToggleFlagListener = new Runnable() { + @Override + public void run() { + setWaitingForTransitionStart(false); + } + }; + private TrustManager mTrustManager; protected Context mContext; protected Handler mHandler; TaskStackListenerImpl mTaskStackListener; @@ -255,7 +255,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // When this fires, then the user has not released alt-tab for at least // FAST_ALT_TAB_DELAY_MS milliseconds showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */, - false /* reloadTasks */, false /* fromHome */, DividerView.INVALID_RECENTS_GROW_TARGET); } }); @@ -270,25 +269,26 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // Register the task stack listener mTaskStackListener = new TaskStackListenerImpl(); - SystemServicesProxy ssp = Recents.getSystemServices(); - ssp.registerTaskStackListener(mTaskStackListener); + ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); // Initialize the static configuration resources mDummyStackView = new TaskStackView(mContext); reloadResources(); + + mTrustManager = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE); } public void onBootCompleted() { // When we start, preload the data associated with the previous recent tasks. // We can use a new plan since the caches will be the same. RecentsTaskLoader loader = Recents.getTaskLoader(); - RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); - loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */); + RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext); + loader.preloadTasks(plan, -1); RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); launchOpts.numVisibleTasks = loader.getIconCacheSize(); launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize(); launchOpts.onlyLoadForCache = true; - loader.loadTasks(mContext, plan, launchOpts); + loader.loadTasks(plan, launchOpts); } public void onConfigurationChanged() { @@ -314,16 +314,22 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}. */ public void onStartScreenPinning(Context context, int taskId) { - SystemUIApplication app = (SystemUIApplication) context; - StatusBar statusBar = app.getComponent(StatusBar.class); + final StatusBar statusBar = getStatusBar(); if (statusBar != null) { statusBar.showScreenPinningRequest(taskId, false); } } public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents, - boolean animate, boolean launchedWhileDockingTask, boolean fromHome, - int growTarget) { + boolean animate, int growTarget) { + final SystemServicesProxy ssp = Recents.getSystemServices(); + final MutableBoolean isHomeStackVisible = new MutableBoolean(true); + final boolean isRecentsVisible = Recents.getSystemServices().isRecentsActivityVisible( + isHomeStackVisible); + final boolean fromHome = isHomeStackVisible.value; + final boolean launchedWhileDockingTask = + Recents.getSystemServices().getSplitScreenPrimaryStack() != null; + mTriggeredFromAltTab = triggeredFromAltTab; mDraggingInRecents = draggingInRecents; mLaunchedWhileDocking = launchedWhileDockingTask; @@ -349,13 +355,12 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener try { // Check if the top task is in the home stack, and start the recents activity - SystemServicesProxy ssp = Recents.getSystemServices(); - boolean forceVisible = launchedWhileDockingTask || draggingInRecents; - MutableBoolean isHomeStackVisible = new MutableBoolean(forceVisible); - if (forceVisible || !ssp.isRecentsActivityVisible(isHomeStackVisible)) { - ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask(); - startRecentsActivity(runningTask, isHomeStackVisible.value || fromHome, animate, - growTarget); + final boolean forceVisible = launchedWhileDockingTask || draggingInRecents; + if (forceVisible || !isRecentsVisible) { + ActivityManager.RunningTaskInfo runningTask = + ActivityManagerWrapper.getInstance().getRunningTask(); + startRecentsActivityAndDismissKeyguardIfNeeded(runningTask, + isHomeStackVisible.value || fromHome, animate, growTarget); } } catch (ActivityNotFoundException e) { Log.e(TAG, "Failed to launch RecentsActivity", e); @@ -381,8 +386,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener public void toggleRecents(int growTarget) { // Skip preloading if the task is locked - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.isScreenPinningActive()) { + if (ActivityManagerWrapper.getInstance().isLockToAppActive()) { return; } @@ -404,27 +408,22 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener MutableBoolean isHomeStackVisible = new MutableBoolean(true); long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime; + SystemServicesProxy ssp = Recents.getSystemServices(); if (ssp.isRecentsActivityVisible(isHomeStackVisible)) { - RecentsDebugFlags debugFlags = Recents.getDebugFlags(); RecentsConfiguration config = Recents.getConfiguration(); RecentsActivityLaunchState launchState = config.getLaunchState(); if (!launchState.launchedWithAltTab) { - // Has the user tapped quickly? - boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout(); if (Recents.getConfiguration().isGridEnabled) { + // Has the user tapped quickly? + boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout(); if (isQuickTap) { EventBus.getDefault().post(new LaunchNextTaskRequestEvent()); } else { EventBus.getDefault().post(new LaunchMostRecentTaskRequestEvent()); } } else { - if (!debugFlags.isPagingEnabled() || isQuickTap) { - // Launch the next focused task - EventBus.getDefault().post(new LaunchNextTaskRequestEvent()); - } else { - // Notify recents to move onto the next task - EventBus.getDefault().post(new IterateRecentsEvent()); - } + // Launch the next focused task + EventBus.getDefault().post(new LaunchNextTaskRequestEvent()); } } else { // If the user has toggled it too quickly, then just eat up the event here (it's @@ -449,12 +448,14 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener } // Otherwise, start the recents activity - ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask(); - startRecentsActivity(runningTask, isHomeStackVisible.value, true /* animate */, - growTarget); + ActivityManager.RunningTaskInfo runningTask = + ActivityManagerWrapper.getInstance().getRunningTask(); + startRecentsActivityAndDismissKeyguardIfNeeded(runningTask, + isHomeStackVisible.value, true /* animate */, growTarget); // Only close the other system windows if we are actually showing recents - ssp.sendCloseSystemWindows(StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS); + ActivityManagerWrapper.getInstance().closeSystemWindows( + SYSTEM_DIALOG_REASON_RECENT_APPS); mLastToggleTime = SystemClock.elapsedRealtime(); } } catch (ActivityNotFoundException e) { @@ -464,8 +465,13 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener public void preloadRecents() { // Skip preloading if the task is locked - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.isScreenPinningActive()) { + if (ActivityManagerWrapper.getInstance().isLockToAppActive()) { + return; + } + + // Skip preloading recents when keyguard is showing + final StatusBar statusBar = getStatusBar(); + if (statusBar != null && statusBar.isKeyguardShowing()) { return; } @@ -473,16 +479,17 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // RecentsActivity) only if there is a task to animate to. Post this to ensure that we // don't block the touch feedback on the nav bar button which triggers this. mHandler.post(() -> { - MutableBoolean isHomeStackVisible = new MutableBoolean(true); - if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) { - ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask(); + SystemServicesProxy ssp = Recents.getSystemServices(); + if (!ssp.isRecentsActivityVisible(null)) { + ActivityManager.RunningTaskInfo runningTask = + ActivityManagerWrapper.getInstance().getRunningTask(); if (runningTask == null) { return; } RecentsTaskLoader loader = Recents.getTaskLoader(); - sInstanceLoadPlan = loader.createLoadPlan(mContext); - loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value); + sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext); + loader.preloadTasks(sInstanceLoadPlan, runningTask.id); TaskStack stack = sInstanceLoadPlan.getTaskStack(); if (stack.getTaskCount() > 0) { // Only preload the icon (but not the thumbnail since it may not have been taken @@ -521,20 +528,23 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener public void showNextTask() { SystemServicesProxy ssp = Recents.getSystemServices(); RecentsTaskLoader loader = Recents.getTaskLoader(); - RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); - loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */); + RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext); + loader.preloadTasks(plan, -1); TaskStack focusedStack = plan.getTaskStack(); // Return early if there are no tasks in the focused stack if (focusedStack == null || focusedStack.getTaskCount() == 0) return; // Return early if there is no running task - ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask(); + ActivityManager.RunningTaskInfo runningTask = + ActivityManagerWrapper.getInstance().getRunningTask(); if (runningTask == null) return; // Find the task in the recents list - boolean isRunningTaskInHomeStack = SystemServicesProxy.isHomeStack(runningTask.stackId); - ArrayList<Task> tasks = focusedStack.getStackTasks(); + boolean isRunningTaskInHomeStack = + runningTask.configuration.windowConfiguration.getActivityType() + == ACTIVITY_TYPE_HOME; + ArrayList<Task> tasks = focusedStack.getTasks(); Task toTask = null; ActivityOptions launchOpts = null; int taskCount = tasks.size(); @@ -564,9 +574,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener } // Launch the task - ssp.startActivityFromRecents( - mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID, - null /* resultListener */); + ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(toTask.key, launchOpts, + null /* resultCallback */, null /* resultCallbackHandler */); } /** @@ -575,61 +584,58 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener public void showRelativeAffiliatedTask(boolean showNextTask) { SystemServicesProxy ssp = Recents.getSystemServices(); RecentsTaskLoader loader = Recents.getTaskLoader(); - RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); - loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */); + RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext); + loader.preloadTasks(plan, -1); TaskStack focusedStack = plan.getTaskStack(); // Return early if there are no tasks in the focused stack if (focusedStack == null || focusedStack.getTaskCount() == 0) return; // Return early if there is no running task (can't determine affiliated tasks in this case) - ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask(); + ActivityManager.RunningTaskInfo runningTask = + ActivityManagerWrapper.getInstance().getRunningTask(); + final int activityType = runningTask.configuration.windowConfiguration.getActivityType(); if (runningTask == null) return; // Return early if the running task is in the home/recents stack (optimization) - if (isHomeOrRecentsStack(runningTask.stackId)) return; + if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) return; // Find the task in the recents list - ArrayList<Task> tasks = focusedStack.getStackTasks(); + ArrayList<Task> tasks = focusedStack.getTasks(); Task toTask = null; ActivityOptions launchOpts = null; int taskCount = tasks.size(); - int numAffiliatedTasks = 0; for (int i = 0; i < taskCount; i++) { Task task = tasks.get(i); if (task.key.id == runningTask.id) { - TaskGrouping group = task.group; - Task.TaskKey toTaskKey; if (showNextTask) { - toTaskKey = group.getNextTaskInGroup(task); - launchOpts = ActivityOptions.makeCustomAnimation(mContext, - R.anim.recents_launch_next_affiliated_task_target, - R.anim.recents_launch_next_affiliated_task_source); + if ((i + 1) < taskCount) { + toTask = tasks.get(i + 1); + launchOpts = ActivityOptions.makeCustomAnimation(mContext, + R.anim.recents_launch_next_affiliated_task_target, + R.anim.recents_launch_next_affiliated_task_source); + } } else { - toTaskKey = group.getPrevTaskInGroup(task); - launchOpts = ActivityOptions.makeCustomAnimation(mContext, - R.anim.recents_launch_prev_affiliated_task_target, - R.anim.recents_launch_prev_affiliated_task_source); - } - if (toTaskKey != null) { - toTask = focusedStack.findTaskWithId(toTaskKey.id); + if ((i - 1) >= 0) { + toTask = tasks.get(i - 1); + launchOpts = ActivityOptions.makeCustomAnimation(mContext, + R.anim.recents_launch_prev_affiliated_task_target, + R.anim.recents_launch_prev_affiliated_task_source); + } } - numAffiliatedTasks = group.getTaskCount(); break; } } // Return early if there is no next task if (toTask == null) { - if (numAffiliatedTasks > 1) { - if (showNextTask) { - ssp.startInPlaceAnimationOnFrontMostApplication( - ActivityOptions.makeCustomInPlaceAnimation(mContext, - R.anim.recents_launch_next_affiliated_task_bounce)); - } else { - ssp.startInPlaceAnimationOnFrontMostApplication( - ActivityOptions.makeCustomInPlaceAnimation(mContext, - R.anim.recents_launch_prev_affiliated_task_bounce)); - } + if (showNextTask) { + ssp.startInPlaceAnimationOnFrontMostApplication( + ActivityOptions.makeCustomInPlaceAnimation(mContext, + R.anim.recents_launch_next_affiliated_task_bounce)); + } else { + ssp.startInPlaceAnimationOnFrontMostApplication( + ActivityOptions.makeCustomInPlaceAnimation(mContext, + R.anim.recents_launch_prev_affiliated_task_bounce)); } return; } @@ -638,9 +644,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1); // Launch the task - ssp.startActivityFromRecents( - mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID, - null /* resultListener */); + ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(toTask.key, launchOpts, + null /* resultListener */, null /* resultCallbackHandler */); } public void showNextAffiliatedTask() { @@ -655,21 +660,14 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener showRelativeAffiliatedTask(false); } - public void dockTopTask(int topTaskId, int dragMode, - int stackCreateMode, Rect initialBounds) { + public void splitPrimaryTask(int taskId, int dragMode, int stackCreateMode, + Rect initialBounds) { SystemServicesProxy ssp = Recents.getSystemServices(); // Make sure we inform DividerView before we actually start the activity so we can change // the resize mode already. - if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) { + if (ssp.setTaskWindowingModeSplitScreenPrimary(taskId, stackCreateMode, initialBounds)) { EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds)); - showRecents( - false /* triggeredFromAltTab */, - dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS, - false /* animate */, - true /* launchedWhileDockingTask*/, - false /* fromHome */, - DividerView.INVALID_RECENTS_GROW_TARGET); } } @@ -740,7 +738,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // However, the window bounds include the insets, so we need to subtract them here to make // them identical. if (ssp.hasDockedTask()) { - windowRect.bottom -= systemInsets.bottom; + if (systemInsets.bottom < windowRect.height()) { + // Only apply inset if it isn't going to cause the rect height to go negative. + windowRect.bottom -= systemInsets.bottom; + } systemInsets.bottom = 0; } calculateWindowStableInsets(systemInsets, windowRect, displayRect); @@ -752,8 +753,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top, systemInsets.left, systemInsets.right, mTmpBounds); stackLayout.reset(); - stackLayout.initialize(displayRect, windowRect, mTmpBounds, - TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack)); + stackLayout.initialize(displayRect, windowRect, mTmpBounds); } } @@ -842,7 +842,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener launchOpts.runningTaskId = runningTaskId; launchOpts.loadThumbnails = false; launchOpts.onlyLoadForCache = true; - Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts); + Recents.getTaskLoader().loadTasks(sInstanceLoadPlan, launchOpts); } /** @@ -872,59 +872,29 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask, Rect windowOverrideRect) { final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice; - if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) { - ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>(); - ArrayList<Task> tasks = mDummyStackView.getStack().getStackTasks(); - TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm(); - TaskStackViewScroller stackScroller = mDummyStackView.getScroller(); - - mDummyStackView.updateLayoutAlgorithm(true /* boundScroll */); - mDummyStackView.updateToInitialState(); - - for (int i = tasks.size() - 1; i >= 0; i--) { - Task task = tasks.get(i); - if (task.isFreeformTask()) { - mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task, - stackScroller.getStackScroll(), mTmpTransform, null, - windowOverrideRect); - GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform); - Rect toTaskRect = new Rect(); - mTmpTransform.rect.round(toTaskRect); - specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect)); - } + + // Update the destination rect + Task toTask = new Task(); + TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask, + windowOverrideRect); + + RectF toTaskRect = toTransform.rect; + AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(mHandler) { + @Override + public List<AppTransitionAnimationSpecCompat> composeSpecs() { + Rect rect = new Rect(); + toTaskRect.round(rect); + Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform); + return Lists.newArrayList(new AppTransitionAnimationSpecCompat(toTask.key.id, + thumbnail, rect)); } - AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()]; - specs.toArray(specsArray); - - // For low end ram devices, wait for transition flag is reset when Recents entrance - // animation is complete instead of when the transition animation starts - return new Pair<>(ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, - specsArray, mHandler, isLowRamDevice ? null : mResetToggleFlagListener, this), - null); - } else { - // Update the destination rect - Task toTask = new Task(); - TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask, - windowOverrideRect); - - RectF toTaskRect = toTransform.rect; - AppTransitionAnimationSpecsFuture future = - new RecentsTransitionHelper(mContext).getAppTransitionFuture( - () -> { - Rect rect = new Rect(); - toTaskRect.round(rect); - GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask, - toTransform); - return Lists.newArrayList(new AppTransitionAnimationSpec( - toTask.key.id, thumbnail, rect)); - }); - - // For low end ram devices, wait for transition flag is reset when Recents entrance - // animation is complete instead of when the transition animation starts - return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext, - mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener, - false /* scaleUp */), future); - } + }; + + // For low end ram devices, wait for transition flag is reset when Recents entrance + // animation is complete instead of when the transition animation starts + return new Pair<>(RecentsTransition.createAspectScaleAnimation(mContext, mHandler, + false /* scaleUp */, future, isLowRamDevice ? null : mResetToggleFlagListener), + future); } /** @@ -939,7 +909,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener runningTaskOut.copyFrom(launchTask); } else { // If no task is specified or we can not find the task just use the front most one - launchTask = stack.getStackFrontMostTask(true /* includeFreeform */); + launchTask = stack.getFrontMostTask(); runningTaskOut.copyFrom(launchTask); } @@ -954,7 +924,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener /** * Draws the header of a task used for the window animation into a bitmap. */ - private GraphicBuffer drawThumbnailTransitionBitmap(Task toTask, + private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) { SystemServicesProxy ssp = Recents.getSystemServices(); int width = (int) toTransform.rect.width(); @@ -964,7 +934,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode(); mHeaderBar.onTaskViewSizeChanged(width, height); if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) { - return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight, + return RecentsTransition.drawViewIntoHardwareBitmap(width, mTaskBarHeight, null, 1f, 0xFFff0000); } else { // Workaround for b/27815919, reset the callback so that we do not trigger an @@ -977,7 +947,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener disabledInSafeMode); mHeaderBar.onTaskDataLoaded(); mHeaderBar.setDimAlpha(toTransform.dimAlpha); - return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight, + return RecentsTransition.drawViewIntoHardwareBitmap(width, mTaskBarHeight, mHeaderBar, 1f, 0); } } @@ -986,18 +956,32 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener } /** - * Shows the recents activity + * Shows the recents activity after dismissing the keyguard if visible */ - protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask, + protected void startRecentsActivityAndDismissKeyguardIfNeeded( + final ActivityManager.RunningTaskInfo runningTask, final boolean isHomeStackVisible, + final boolean animate, final int growTarget) { + // Preload only if device for current user is unlocked + final StatusBar statusBar = getStatusBar(); + if (statusBar != null && statusBar.isKeyguardShowing()) { + statusBar.executeRunnableDismissingKeyguard(() -> { + // Flush trustmanager before checking device locked per user when preloading + mTrustManager.reportKeyguardShowingChanged(); + mHandler.post(() -> startRecentsActivity(runningTask, isHomeStackVisible, + animate, growTarget)); + }, null, true /* dismissShade */, false /* afterKeyguardGone */, + true /* deferred */); + } else { + startRecentsActivity(runningTask, isHomeStackVisible, animate, growTarget); + } + } + + private void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask, boolean isHomeStackVisible, boolean animate, int growTarget) { RecentsTaskLoader loader = Recents.getTaskLoader(); RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); - SystemServicesProxy ssp = Recents.getSystemServices(); - boolean isBlacklisted = (runningTask != null) - ? ssp.isBlackListedActivity(runningTask.baseActivity.getClassName()) - : false; - int runningTaskId = !mLaunchedWhileDocking && !isBlacklisted && (runningTask != null) + int runningTaskId = !mLaunchedWhileDocking && (runningTask != null) ? runningTask.id : -1; @@ -1006,10 +990,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // the stacks might have changed. if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) { // Create a new load plan if preloadRecents() was never triggered - sInstanceLoadPlan = loader.createLoadPlan(mContext); + sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext); } if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) { - loader.preloadTasks(sInstanceLoadPlan, runningTaskId, !isHomeStackVisible); + loader.preloadTasks(sInstanceLoadPlan, runningTaskId); } TaskStack stack = sInstanceLoadPlan.getTaskStack(); @@ -1020,7 +1004,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // Update the launch state that we need in updateHeaderBarLayout() launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking; launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking; - launchState.launchedFromBlacklistedApp = launchState.launchedFromApp && isBlacklisted; launchState.launchedFromPipApp = false; launchState.launchedWithNextPipApp = stack.isNextLaunchTargetPip(RecentsImpl.getLastPipTime()); @@ -1056,9 +1039,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener } Pair<ActivityOptions, AppTransitionAnimationSpecsFuture> pair; - if (isBlacklisted) { - pair = new Pair<>(getUnknownTransitionActivityOptions(), null); - } else if (useThumbnailTransition) { + if (useThumbnailTransition) { // Try starting with a thumbnail transition pair = getThumbnailTransitionActivityOptions(runningTask, windowOverrideRect); } else { @@ -1084,6 +1065,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener return result; } + private StatusBar getStatusBar() { + return ((SystemUIApplication) mContext).getComponent(StatusBar.class); + } + /** * Starts the recents activity. */ @@ -1099,7 +1084,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener Recents.getSystemServices().startActivityAsUserAsync(intent, opts); EventBus.getDefault().send(new RecentsActivityStartingEvent()); if (future != null) { - future.precacheSpecs(); + future.composeSpecsSynchronous(); } }); EventBus.getDefault().send(hideMenuEvent); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java index ff9e89e9e00b..beec4b395e9c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java @@ -58,15 +58,12 @@ public class RecentsImplProxy extends IRecentsNonSystemUserCallbacks.Stub { @Override public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents, boolean animate, - boolean reloadTasks, boolean fromHome, int growTarget) - throws RemoteException { + int growTarget) throws RemoteException { SomeArgs args = SomeArgs.obtain(); args.argi1 = triggeredFromAltTab ? 1 : 0; args.argi2 = draggingInRecents ? 1 : 0; args.argi3 = animate ? 1 : 0; - args.argi4 = reloadTasks ? 1 : 0; - args.argi5 = fromHome ? 1 : 0; - args.argi6 = growTarget; + args.argi4 = growTarget; mHandler.sendMessage(mHandler.obtainMessage(MSG_SHOW_RECENTS, args)); } @@ -90,7 +87,7 @@ public class RecentsImplProxy extends IRecentsNonSystemUserCallbacks.Stub { } @Override - public void dockTopTask(int topTaskId, int dragMode, int stackCreateMode, + public void splitPrimaryTask(int topTaskId, int dragMode, int stackCreateMode, Rect initialBounds) throws RemoteException { SomeArgs args = SomeArgs.obtain(); args.argi1 = topTaskId; @@ -130,7 +127,7 @@ public class RecentsImplProxy extends IRecentsNonSystemUserCallbacks.Stub { case MSG_SHOW_RECENTS: args = (SomeArgs) msg.obj; mImpl.showRecents(args.argi1 != 0, args.argi2 != 0, args.argi3 != 0, - args.argi4 != 0, args.argi5 != 0, args.argi6); + args.argi4); break; case MSG_HIDE_RECENTS: mImpl.hideRecents(msg.arg1 != 0, msg.arg2 != 0); @@ -144,7 +141,7 @@ public class RecentsImplProxy extends IRecentsNonSystemUserCallbacks.Stub { break; case MSG_DOCK_TOP_TASK: args = (SomeArgs) msg.obj; - mImpl.dockTopTask(args.argi1, args.argi2, args.argi3 = 0, + mImpl.splitPrimaryTask(args.argi1, args.argi2, args.argi3 = 0, (Rect) args.arg1); break; case MSG_ON_DRAGGING_IN_RECENTS: diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java new file mode 100644 index 000000000000..0ff8b085f035 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java @@ -0,0 +1,255 @@ +/* + * 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.recents; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; + +import android.annotation.TargetApi; +import android.app.ActivityManager; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.RippleDrawable; +import android.os.Build; +import android.text.TextUtils; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.systemui.OverviewProxyService; +import com.android.systemui.Prefs; +import com.android.systemui.R; +import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; +import com.android.systemui.shared.system.ActivityManagerWrapper; + +/** + * Shows onboarding for the new recents interaction in P (codenamed quickstep). + */ +@TargetApi(Build.VERSION_CODES.P) +public class RecentsOnboarding { + + private static final String TAG = "RecentsOnboarding"; + private static final boolean RESET_PREFS_FOR_DEBUG = false; + private static final long SHOW_DELAY_MS = 500; + private static final long SHOW_HIDE_DURATION_MS = 300; + // Don't show the onboarding until the user has launched this number of apps. + private static final int SHOW_ON_APP_LAUNCH = 2; + + private final Context mContext; + private final WindowManager mWindowManager; + private final OverviewProxyService mOverviewProxyService; + private final View mLayout; + private final TextView mTextView; + private final ImageView mDismissView; + private final ColorDrawable mBackgroundDrawable; + private final int mDarkBackgroundColor; + private final int mLightBackgroundColor; + private final int mDarkContentColor; + private final int mLightContentColor; + private final RippleDrawable mDarkRipple; + private final RippleDrawable mLightRipple; + + private boolean mTaskListenerRegistered; + private boolean mLayoutAttachedToWindow; + private boolean mBackgroundIsLight; + + private final SysUiTaskStackChangeListener mTaskListener = new SysUiTaskStackChangeListener() { + @Override + public void onTaskStackChanged() { + ActivityManager.RunningTaskInfo info = ActivityManagerWrapper.getInstance() + .getRunningTask(ACTIVITY_TYPE_UNDEFINED /* ignoreActivityType */); + int activityType = info.configuration.windowConfiguration.getActivityType(); + int numAppsLaunched = Prefs.getInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0); + if (activityType == ACTIVITY_TYPE_STANDARD) { + numAppsLaunched++; + if (numAppsLaunched >= SHOW_ON_APP_LAUNCH) { + show(); + } else { + Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, numAppsLaunched); + } + } else { + hide(false); + } + } + }; + + private final View.OnAttachStateChangeListener mOnAttachStateChangeListener + = new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View view) { + if (view == mLayout) { + mLayoutAttachedToWindow = true; + } + } + + @Override + public void onViewDetachedFromWindow(View view) { + if (view == mLayout) { + mLayoutAttachedToWindow = false; + } + } + }; + + public RecentsOnboarding(Context context, OverviewProxyService overviewProxyService) { + mContext = context; + mOverviewProxyService = overviewProxyService; + final Resources res = context.getResources(); + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_onboarding, null); + mTextView = mLayout.findViewById(R.id.onboarding_text); + mDismissView = mLayout.findViewById(R.id.dismiss); + mDarkBackgroundColor = res.getColor(android.R.color.background_dark); + mLightBackgroundColor = res.getColor(android.R.color.background_light); + mDarkContentColor = res.getColor(R.color.primary_text_default_material_light); + mLightContentColor = res.getColor(R.color.primary_text_default_material_dark); + mDarkRipple = new RippleDrawable(res.getColorStateList(R.color.ripple_material_light), + null, null); + mLightRipple = new RippleDrawable(res.getColorStateList(R.color.ripple_material_dark), + null, null); + mBackgroundDrawable = new ColorDrawable(mDarkBackgroundColor); + + mLayout.addOnAttachStateChangeListener(mOnAttachStateChangeListener); + mLayout.setBackground(mBackgroundDrawable); + mDismissView.setOnClickListener(v -> hide(true)); + + if (RESET_PREFS_FOR_DEBUG) { + Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false); + Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0); + } + } + + public void onConnectedToLauncher() { + boolean alreadySeenRecentsOnboarding = Prefs.getBoolean(mContext, + Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false); + if (!mTaskListenerRegistered && !alreadySeenRecentsOnboarding) { + ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener); + mTaskListenerRegistered = true; + } + } + + public void onRecentsAnimationStarted() { + boolean alreadySeenRecentsOnboarding = Prefs.getBoolean(mContext, + Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false); + if (!alreadySeenRecentsOnboarding) { + Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, true); + onDisconnectedFromLauncher(); + } + } + + public void onDisconnectedFromLauncher() { + if (mTaskListenerRegistered) { + ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskListener); + mTaskListenerRegistered = false; + } + hide(false); + } + + public void onConfigurationChanged(Configuration newConfiguration) { + if (newConfiguration.orientation != Configuration.ORIENTATION_PORTRAIT) { + hide(false); + } + } + + public void show() { + CharSequence onboardingText = mOverviewProxyService.getOnboardingText(); + if (TextUtils.isEmpty(onboardingText)) { + Log.w(TAG, "Unable to get onboarding text"); + return; + } + mTextView.setText(onboardingText); + // Only show in portrait. + int orientation = mContext.getResources().getConfiguration().orientation; + if (!mLayoutAttachedToWindow && orientation == Configuration.ORIENTATION_PORTRAIT) { + mLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + mWindowManager.addView(mLayout, getWindowLayoutParams()); + int layoutHeight = mLayout.getHeight(); + if (layoutHeight == 0) { + mLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + layoutHeight = mLayout.getMeasuredHeight(); + } + mLayout.setTranslationY(layoutHeight); + mLayout.setAlpha(0); + mLayout.animate() + .translationY(0) + .alpha(1f) + .withLayer() + .setStartDelay(SHOW_DELAY_MS) + .setDuration(SHOW_HIDE_DURATION_MS) + .setInterpolator(new DecelerateInterpolator()) + .start(); + } + } + + public void hide(boolean animate) { + if (mLayoutAttachedToWindow) { + if (animate) { + mLayout.animate() + .translationY(mLayout.getHeight()) + .alpha(0f) + .withLayer() + .setDuration(SHOW_HIDE_DURATION_MS) + .setInterpolator(new AccelerateInterpolator()) + .withEndAction(() -> mWindowManager.removeView(mLayout)) + .start(); + } else { + mWindowManager.removeView(mLayout); + } + } + } + + public void setContentDarkIntensity(float contentDarkIntensity) { + boolean backgroundIsLight = contentDarkIntensity > 0.5f; + if (backgroundIsLight != mBackgroundIsLight) { + mBackgroundIsLight = backgroundIsLight; + mBackgroundDrawable.setColor(mBackgroundIsLight + ? mLightBackgroundColor : mDarkBackgroundColor); + int contentColor = mBackgroundIsLight ? mDarkContentColor : mLightContentColor; + mTextView.setTextColor(contentColor); + mTextView.getCompoundDrawables()[3].setColorFilter(contentColor, + PorterDuff.Mode.SRC_IN); + mDismissView.setColorFilter(contentColor); + mDismissView.setBackground(mBackgroundIsLight ? mDarkRipple : mLightRipple); + } + } + + private WindowManager.LayoutParams getWindowLayoutParams() { + int flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG, + flags, + PixelFormat.TRANSLUCENT); + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; + lp.setTitle("RecentsOnboarding"); + lp.gravity = Gravity.BOTTOM; + return lp; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java index 1285626015d2..ff1f7dc5a2a8 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java @@ -27,6 +27,7 @@ import android.util.SparseArray; import com.android.systemui.EventLogConstants; import com.android.systemui.EventLogTags; import com.android.systemui.recents.events.EventBus; +import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent; import com.android.systemui.recents.events.activity.DockedTopTaskEvent; import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent; @@ -108,6 +109,11 @@ public class RecentsSystemUser extends IRecentsSystemUserCallbacks.Stub { } @Override + public void sendDockedFirstAnimationFrameEvent() throws RemoteException { + EventBus.getDefault().post(new DockedFirstAnimationFrameEvent()); + } + + @Override public void setWaitingForTransitionStartEvent(boolean waitingForTransitionStart) { EventBus.getDefault().post(new SetWaitingForTransitionStartEvent( waitingForTransitionStart)); diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index 316ad1661898..57f7818eae58 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -23,15 +23,12 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.res.Configuration; import android.graphics.PixelFormat; -import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.os.Binder; import android.os.RemoteException; import android.util.DisplayMetrics; import android.view.Gravity; -import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -43,6 +40,9 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.systemui.R; +import com.android.systemui.SysUiServiceProvider; +import com.android.systemui.statusbar.phone.NavigationBarView; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.util.leak.RotationUtils; import java.util.ArrayList; @@ -233,11 +233,30 @@ public class ScreenPinningRequest implements View.OnClickListener { .setVisibility(View.INVISIBLE); } + StatusBar statusBar = SysUiServiceProvider.getComponent(mContext, StatusBar.class); + NavigationBarView navigationBarView = statusBar.getNavigationBarView(); + final boolean recentsVisible = navigationBarView != null + && navigationBarView.isRecentsButtonVisible(); boolean touchExplorationEnabled = mAccessibilityService.isTouchExplorationEnabled(); + int descriptionStringResId; + if (recentsVisible) { + mLayout.findViewById(R.id.screen_pinning_recents_group).setVisibility(VISIBLE); + mLayout.findViewById(R.id.screen_pinning_home_bg_light).setVisibility(INVISIBLE); + mLayout.findViewById(R.id.screen_pinning_home_bg).setVisibility(INVISIBLE); + descriptionStringResId = touchExplorationEnabled + ? R.string.screen_pinning_description_accessible + : R.string.screen_pinning_description; + } else { + mLayout.findViewById(R.id.screen_pinning_recents_group).setVisibility(INVISIBLE); + mLayout.findViewById(R.id.screen_pinning_home_bg_light).setVisibility(VISIBLE); + mLayout.findViewById(R.id.screen_pinning_home_bg).setVisibility(VISIBLE); + descriptionStringResId = touchExplorationEnabled + ? R.string.screen_pinning_description_recents_invisible_accessible + : R.string.screen_pinning_description_recents_invisible; + } + ((TextView) mLayout.findViewById(R.id.screen_pinning_description)) - .setText(touchExplorationEnabled - ? R.string.screen_pinning_description_accessible - : R.string.screen_pinning_description); + .setText(descriptionStringResId); final int backBgVisibility = touchExplorationEnabled ? View.INVISIBLE : View.VISIBLE; mLayout.findViewById(R.id.screen_pinning_back_bg).setVisibility(backBgVisibility); mLayout.findViewById(R.id.screen_pinning_back_bg_light).setVisibility(backBgVisibility); diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java index 7604de1d05d0..fec34e3cd23d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.activity; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.model.Task; +import com.android.systemui.shared.recents.model.Task; /** * This is sent when we want to cancel the enter-recents window animation for the launch task. diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java deleted file mode 100644 index fe3bf26334b9..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.events.activity; - -import com.android.systemui.recents.events.EventBus; - -/** - * This is sent when the SystemUI tuner changes a flag. - */ -public class DebugFlagsChangedEvent extends EventBus.Event { - // Simple event -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java deleted file mode 100644 index f7b2706b9c57..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.events.activity; - -import com.android.systemui.recents.events.EventBus; - -/** - * This is sent when the user taps on the Overview button to iterate to the next item in the - * Recents list. - */ -public class IterateRecentsEvent extends EventBus.Event { - // Simple event -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java index 3db106e7200f..2409f39d3760 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java @@ -16,10 +16,13 @@ package com.android.systemui.recents.events.activity; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; + import android.graphics.Rect; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.model.Task; +import com.android.systemui.shared.recents.model.Task; import com.android.systemui.recents.views.TaskView; /** @@ -30,15 +33,23 @@ public class LaunchTaskEvent extends EventBus.Event { public final TaskView taskView; public final Task task; public final Rect targetTaskBounds; - public final int targetTaskStack; + public final int targetWindowingMode; + public final int targetActivityType; public final boolean screenPinningRequested; - public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds, int targetTaskStack, + public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds, boolean screenPinningRequested) { + this(taskView, task, targetTaskBounds, screenPinningRequested, + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED); + } + + public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds, + boolean screenPinningRequested, int windowingMode, int activityType) { this.taskView = taskView; this.task = task; this.targetTaskBounds = targetTaskBounds; - this.targetTaskStack = targetTaskStack; + this.targetWindowingMode = windowingMode; + this.targetActivityType = activityType; this.screenPinningRequested = screenPinningRequested; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java index 64eeafa1ae17..e4972b1fd7f4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.activity; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.shared.recents.model.TaskStack; /** * This is sent by the activity whenever the multi-window state has changed. diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java index 3b68574c2830..47670e03c6a1 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java @@ -17,22 +17,20 @@ package com.android.systemui.recents.events.activity; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.model.RecentsPackageMonitor; import com.android.systemui.recents.views.TaskStackView; +import com.android.systemui.recents.RecentsActivity; /** - * This event is sent by {@link RecentsPackageMonitor} when a package on the the system changes. + * This event is sent by {@link RecentsActivity} when a package on the the system changes. * {@link TaskStackView}s listen for this event, and remove the tasks associated with the removed * packages. */ public class PackagesChangedEvent extends EventBus.Event { - public final RecentsPackageMonitor monitor; public final String packageName; public final int userId; - public PackagesChangedEvent(RecentsPackageMonitor monitor, String packageName, int userId) { - this.monitor = monitor; + public PackagesChangedEvent(String packageName, int userId) { this.packageName = packageName; this.userId = userId; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java index 0d614e8c675c..51d02b5b0018 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.activity; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.shared.recents.model.TaskStack; /** * This is sent by the activity whenever the task stach has changed. diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java index 8fe4975f1a2a..37266f6ff39f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java @@ -22,5 +22,4 @@ import com.android.systemui.recents.events.EventBus; * This is sent when the PiP should be expanded due to being relaunched. */ public class ExpandPipEvent extends EventBus.Event { - public final boolean clearThumbnailWindows = true; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java index 4ed027084def..b52e83b81649 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.ui; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.model.Task; +import com.android.systemui.shared.recents.model.Task; /** * This is sent when the data associated with a given {@link Task} should be deleted from the diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java index 40c30b884eae..da19384ae93a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.ui; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.model.Task; +import com.android.systemui.shared.recents.model.Task; /** * This is sent when a user wants to show the application info for a {@link Task}. diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java index e0ed7a9e7e35..f08292801b62 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.ui; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.model.ThumbnailData; +import com.android.systemui.shared.recents.model.ThumbnailData; /** * Sent when a task snapshot has changed. diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java index 0628c5015153..881a64af5b0f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java @@ -17,8 +17,8 @@ package com.android.systemui.recents.events.ui; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.views.AnimationProps; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.utilities.AnimationProps; import com.android.systemui.recents.views.TaskView; /** diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java deleted file mode 100644 index b42da9c7a793..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.events.ui; - -import com.android.systemui.recents.events.EventBus; - -/** - * This is sent to update the visibility of all visible freeform task views. - */ -public class UpdateFreeformTaskViewVisibilityEvent extends EventBus.Event { - - public final boolean visible; - - public UpdateFreeformTaskViewVisibilityEvent(boolean visible) { - this.visible = visible; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java index 216be6121f8d..cf61b1ef7637 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.ui.dragndrop; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.model.Task; +import com.android.systemui.shared.recents.model.Task; import com.android.systemui.recents.views.DropTarget; /** diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java index edd799597ea6..297afc53c557 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java @@ -17,9 +17,8 @@ package com.android.systemui.recents.events.ui.dragndrop; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.TaskStack; -import com.android.systemui.recents.views.DropTarget; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.TaskStack; import com.android.systemui.recents.views.TaskView; /** diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java index 73c282fe8816..73cbde998319 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.ui.dragndrop; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.model.Task; +import com.android.systemui.shared.recents.model.Task; import com.android.systemui.recents.views.DropTarget; import com.android.systemui.recents.views.TaskView; diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java index e57fa2d86a66..021be77bcc8b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java @@ -19,7 +19,7 @@ package com.android.systemui.recents.events.ui.dragndrop; import android.graphics.Point; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.model.Task; +import com.android.systemui.shared.recents.model.Task; import com.android.systemui.recents.views.TaskView; /** diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java index 7030729d2a04..64ba5748bb89 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.ui.dragndrop; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.model.Task; +import com.android.systemui.shared.recents.model.Task; import com.android.systemui.recents.views.RecentsViewTouchHandler; import com.android.systemui.recents.views.TaskView; diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java index a1e4957a2719..171ab5e8bcca 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java @@ -22,10 +22,5 @@ import com.android.systemui.recents.events.EventBus; * Focuses the next task view in the stack. */ public class FocusNextTaskViewEvent extends EventBus.Event { - - public final int timerIndicatorDuration; - - public FocusNextTaskViewEvent(int timerIndicatorDuration) { - this.timerIndicatorDuration = timerIndicatorDuration; - } + // Simple event } diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java b/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java deleted file mode 100644 index 72511de9ec80..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.recents.misc; - -import android.animation.TypeEvaluator; -import android.graphics.RectF; - -/** - * This evaluator can be used to perform type interpolation between <code>RectF</code> values. - */ -public class RectFEvaluator implements TypeEvaluator<RectF> { - - private RectF mRect = new RectF(); - - /** - * This function returns the result of linearly interpolating the start and - * end Rect values, with <code>fraction</code> representing the proportion - * between the start and end values. The calculation is a simple parametric - * calculation on each of the separate components in the Rect objects - * (left, top, right, and bottom). - * - * <p>The object returned will be the <code>reuseRect</code> passed into the constructor.</p> - * - * @param fraction The fraction from the starting to the ending values - * @param startValue The start Rect - * @param endValue The end Rect - * @return A linear interpolation between the start and end values, given the - * <code>fraction</code> parameter. - */ - @Override - public RectF evaluate(float fraction, RectF startValue, RectF endValue) { - float left = startValue.left + ((endValue.left - startValue.left) * fraction); - float top = startValue.top + ((endValue.top - startValue.top) * fraction); - float right = startValue.right + ((endValue.right - startValue.right) * fraction); - float bottom = startValue.bottom + ((endValue.bottom - startValue.bottom) * fraction); - mRect.set(left, top, right, bottom); - return mRect; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/NamedCounter.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java index ec3c39cc4fbc..5d7f1ba5eaf4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/NamedCounter.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * 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. @@ -16,24 +16,20 @@ package com.android.systemui.recents.misc; -/** - * Used to generate successive incremented names. - */ -public class NamedCounter { +import android.content.Context; - int mCount; - String mPrefix = ""; - String mSuffix = ""; +import com.android.systemui.shared.system.TaskStackChangeListener; - public NamedCounter(String prefix, String suffix) { - mPrefix = prefix; - mSuffix = suffix; - } +/** + * An implementation of {@link TaskStackChangeListener}. + */ +public abstract class SysUiTaskStackChangeListener extends TaskStackChangeListener { - /** Returns the next name. */ - public String nextName() { - String name = mPrefix + mCount + mSuffix; - mCount++; - return name; + /** + * Checks that the current user matches the user's SystemUI process. + */ + protected final boolean checkCurrentUserId(Context context, boolean debug) { + int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser(); + return checkCurrentUserId(currentUserId, debug); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index c9ef43e3f372..93fd34aa0519 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -16,66 +16,48 @@ package com.android.systemui.recents.misc; -import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; -import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; -import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; -import static android.app.ActivityManager.StackId.HOME_STACK_ID; -import static android.app.ActivityManager.StackId.INVALID_STACK_ID; -import static android.app.ActivityManager.StackId.PINNED_STACK_ID; -import static android.app.ActivityManager.StackId.RECENTS_STACK_ID; -import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.StackInfo; -import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityOptions; import android.app.AppGlobals; import android.app.IActivityManager; -import android.app.KeyguardManager; +import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.IRemoteCallback; -import android.os.Message; -import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; -import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; -import android.provider.Settings.Secure; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; -import android.util.ArraySet; -import android.util.IconDrawableFactory; import android.util.Log; import android.util.MutableBoolean; import android.view.Display; -import android.view.IAppTransitionAnimationSpecsFuture; import android.view.IDockedStackListener; import android.view.IWindowManager; import android.view.WindowManager; @@ -86,23 +68,12 @@ import android.view.accessibility.AccessibilityManager; import com.android.internal.app.AssistUtils; import com.android.internal.os.BackgroundThread; import com.android.systemui.Dependency; -import com.android.systemui.R; import com.android.systemui.UiOffloadThread; -import com.android.systemui.pip.tv.PipMenuActivity; import com.android.systemui.recents.Recents; -import com.android.systemui.recents.RecentsDebugFlags; import com.android.systemui.recents.RecentsImpl; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.ThumbnailData; import com.android.systemui.statusbar.policy.UserInfoController; -import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; import java.util.List; -import java.util.Random; /** * Acts as a shim around the real system services that we need to access data from, and provides @@ -118,42 +89,30 @@ public class SystemServicesProxy { sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565; } - final static List<String> sRecentsBlacklist; - static { - sRecentsBlacklist = new ArrayList<>(); - sRecentsBlacklist.add(PipMenuActivity.class.getName()); - } - private static SystemServicesProxy sSystemServicesProxy; AccessibilityManager mAccm; ActivityManager mAm; IActivityManager mIam; PackageManager mPm; - IconDrawableFactory mDrawableFactory; IPackageManager mIpm; private final IDreamManager mDreamManager; private final Context mContext; AssistUtils mAssistUtils; WindowManager mWm; IWindowManager mIwm; - KeyguardManager mKgm; UserManager mUm; Display mDisplay; String mRecentsPackage; - ComponentName mAssistComponent; private int mCurrentUserId; boolean mIsSafeMode; - boolean mHasFreeformWorkspaceSupport; - Bitmap mDummyIcon; int mDummyThumbnailWidth; int mDummyThumbnailHeight; Paint mBgProtectionPaint; Canvas mBgProtectionCanvas; - private final Handler mHandler = new H(); private final Runnable mGcRunnable = new Runnable() { @Override public void run() { @@ -164,143 +123,10 @@ public class SystemServicesProxy { private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class); - /** - * An abstract class to track task stack changes. - * Classes should implement this instead of {@link android.app.ITaskStackListener} - * to reduce IPC calls from system services. These callbacks will be called on the main thread. - */ - public abstract static class TaskStackListener { - /** - * NOTE: This call is made of the thread that the binder call comes in on. - */ - public void onTaskStackChangedBackground() { } - public void onTaskStackChanged() { } - public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { } - public void onActivityPinned(String packageName, int userId, int taskId) { } - public void onActivityUnpinned() { } - public void onPinnedActivityRestartAttempt(boolean clearedTask) { } - public void onPinnedStackAnimationStarted() { } - public void onPinnedStackAnimationEnded() { } - public void onActivityForcedResizable(String packageName, int taskId, int reason) { } - public void onActivityDismissingDockedStack() { } - public void onActivityLaunchOnSecondaryDisplayFailed() { } - public void onTaskProfileLocked(int taskId, int userId) { } - - /** - * Checks that the current user matches the user's SystemUI process. Since - * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of - * TaskStackListener should make this call to verify that we don't act on events from other - * user's processes. - */ - protected final boolean checkCurrentUserId(Context context, boolean debug) { - int processUserId = UserHandle.myUserId(); - int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser(); - if (processUserId != currentUserId) { - if (debug) { - Log.d(TAG, "UID mismatch. SystemUI is running uid=" + processUserId - + " and the current user is uid=" + currentUserId); - } - return false; - } - return true; - } - } - - /** - * Implementation of {@link android.app.ITaskStackListener} to listen task stack changes from - * ActivityManagerService. - * This simply passes callbacks to listeners through {@link H}. - * */ - private android.app.TaskStackListener mTaskStackListener = new android.app.TaskStackListener() { - - private final List<SystemServicesProxy.TaskStackListener> mTmpListeners = new ArrayList<>(); - - @Override - public void onTaskStackChanged() throws RemoteException { - // Call the task changed callback for the non-ui thread listeners first - synchronized (mTaskStackListeners) { - mTmpListeners.clear(); - mTmpListeners.addAll(mTaskStackListeners); - } - for (int i = mTmpListeners.size() - 1; i >= 0; i--) { - mTmpListeners.get(i).onTaskStackChangedBackground(); - } - - mHandler.removeMessages(H.ON_TASK_STACK_CHANGED); - mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED); - } - - @Override - public void onActivityPinned(String packageName, int userId, int taskId) - throws RemoteException { - mHandler.removeMessages(H.ON_ACTIVITY_PINNED); - mHandler.obtainMessage(H.ON_ACTIVITY_PINNED, userId, taskId, packageName).sendToTarget(); - } - - @Override - public void onActivityUnpinned() throws RemoteException { - mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED); - mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED); - } - - @Override - public void onPinnedActivityRestartAttempt(boolean clearedTask) - throws RemoteException{ - mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT); - mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT, clearedTask ? 1 : 0, 0) - .sendToTarget(); - } - - @Override - public void onPinnedStackAnimationStarted() throws RemoteException { - mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_STARTED); - mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_STARTED); - } - - @Override - public void onPinnedStackAnimationEnded() throws RemoteException { - mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED); - mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED); - } - - @Override - public void onActivityForcedResizable(String packageName, int taskId, int reason) - throws RemoteException { - mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName) - .sendToTarget(); - } - - @Override - public void onActivityDismissingDockedStack() throws RemoteException { - mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK); - } - - @Override - public void onActivityLaunchOnSecondaryDisplayFailed() throws RemoteException { - mHandler.sendEmptyMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED); - } - - @Override - public void onTaskProfileLocked(int taskId, int userId) { - mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget(); - } - - @Override - public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) - throws RemoteException { - mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget(); - } - }; - private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener = (String name, Drawable picture, String userAccount) -> mCurrentUserId = mAm.getCurrentUser(); - /** - * List of {@link TaskStackListener} registered from {@link #registerTaskStackListener}. - */ - private List<TaskStackListener> mTaskStackListeners = new ArrayList<>(); - /** Private constructor */ private SystemServicesProxy(Context context) { mContext = context.getApplicationContext(); @@ -308,21 +134,15 @@ public class SystemServicesProxy { mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); mIam = ActivityManager.getService(); mPm = context.getPackageManager(); - mDrawableFactory = IconDrawableFactory.newInstance(context); mIpm = AppGlobals.getPackageManager(); mAssistUtils = new AssistUtils(context); mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mIwm = WindowManagerGlobal.getWindowManagerService(); - mKgm = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); mUm = UserManager.get(context); mDreamManager = IDreamManager.Stub.asInterface( ServiceManager.checkService(DreamService.DREAM_SERVICE)); mDisplay = mWm.getDefaultDisplay(); mRecentsPackage = context.getPackageName(); - mHasFreeformWorkspaceSupport = - mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT) || - Settings.Global.getInt(context.getContentResolver(), - DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0; mIsSafeMode = mPm.isSafeMode(); mCurrentUserId = mAm.getCurrentUser(); @@ -339,23 +159,11 @@ public class SystemServicesProxy { mBgProtectionPaint.setColor(0xFFffffff); mBgProtectionCanvas = new Canvas(); - // Resolve the assist intent - mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.myUserId()); - // Since SystemServicesProxy can be accessed from a per-SysUI process component, create a // per-process listener to keep track of the current user id to reduce the number of binder // calls to fetch it. UserInfoController userInfoController = Dependency.get(UserInfoController.class); userInfoController.addCallback(mOnUserInfoChangedListener); - - if (RecentsDebugFlags.Static.EnableMockTasks) { - // Create a dummy icon - mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); - mDummyIcon.eraseColor(0xFF999999); - } - - Collections.addAll(sRecentsBlacklist, - res.getStringArray(R.array.recents_blacklist_array)); } /** @@ -377,130 +185,6 @@ public class SystemServicesProxy { } /** - * @return whether the provided {@param className} is blacklisted - */ - public boolean isBlackListedActivity(String className) { - return sRecentsBlacklist.contains(className); - } - - /** - * Returns a list of the recents tasks. - * - * @param includeFrontMostExcludedTask if set, will ensure that the front most excluded task - * will be visible, otherwise no excluded tasks will be - * visible. - */ - public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId, - boolean includeFrontMostExcludedTask, ArraySet<Integer> quietProfileIds) { - if (mAm == null) return null; - - // If we are mocking, then create some recent tasks - if (RecentsDebugFlags.Static.EnableMockTasks) { - ArrayList<ActivityManager.RecentTaskInfo> tasks = - new ArrayList<ActivityManager.RecentTaskInfo>(); - int count = Math.min(numLatestTasks, RecentsDebugFlags.Static.MockTaskCount); - for (int i = 0; i < count; i++) { - // Create a dummy component name - int packageIndex = i % RecentsDebugFlags.Static.MockTasksPackageCount; - ComponentName cn = new ComponentName("com.android.test" + packageIndex, - "com.android.test" + i + ".Activity"); - String description = "" + i + " - " + - Long.toString(Math.abs(new Random().nextLong()), 36); - // Create the recent task info - ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); - rti.id = rti.persistentId = rti.affiliatedTaskId = i; - rti.baseIntent = new Intent(); - rti.baseIntent.setComponent(cn); - rti.description = description; - rti.firstActiveTime = rti.lastActiveTime = i; - if (i % 2 == 0) { - rti.taskDescription = new ActivityManager.TaskDescription(description, - Bitmap.createBitmap(mDummyIcon), null, - 0xFF000000 | (0xFFFFFF & new Random().nextInt()), - 0xFF000000 | (0xFFFFFF & new Random().nextInt()), - 0, 0); - } else { - rti.taskDescription = new ActivityManager.TaskDescription(); - } - tasks.add(rti); - } - return tasks; - } - - // Remove home/recents/excluded tasks - int minNumTasksToQuery = 10; - int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks); - int flags = ActivityManager.RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS | - ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK | - ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS | - ActivityManager.RECENT_IGNORE_UNAVAILABLE | - ActivityManager.RECENT_INCLUDE_PROFILES; - if (includeFrontMostExcludedTask) { - flags |= ActivityManager.RECENT_WITH_EXCLUDED; - } - List<ActivityManager.RecentTaskInfo> tasks = null; - try { - tasks = mAm.getRecentTasksForUser(numTasksToQuery, flags, userId); - } catch (Exception e) { - Log.e(TAG, "Failed to get recent tasks", e); - } - - // Break early if we can't get a valid set of tasks - if (tasks == null) { - return new ArrayList<>(); - } - - boolean isFirstValidTask = true; - Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator(); - while (iter.hasNext()) { - ActivityManager.RecentTaskInfo t = iter.next(); - - // NOTE: The order of these checks happens in the expected order of the traversal of the - // tasks - - // Remove the task if it or it's package are blacklsited - if (sRecentsBlacklist.contains(t.realActivity.getClassName()) || - sRecentsBlacklist.contains(t.realActivity.getPackageName())) { - iter.remove(); - continue; - } - - // Remove the task if it is marked as excluded, unless it is the first most task and we - // are requested to include it - boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) - == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; - isExcluded |= quietProfileIds.contains(t.userId); - if (isExcluded && (!isFirstValidTask || !includeFrontMostExcludedTask)) { - iter.remove(); - } - - isFirstValidTask = false; - } - - return tasks.subList(0, Math.min(tasks.size(), numLatestTasks)); - } - - /** - * Returns the top running task. - */ - public ActivityManager.RunningTaskInfo getRunningTask() { - // Note: The set of running tasks from the system is ordered by recency - List<ActivityManager.RunningTaskInfo> tasks = mAm.getRunningTasks(10); - if (tasks != null && !tasks.isEmpty()) { - // Find the first task in a valid stack, we ignore everything from the Recents and PiP - // stacks - for (int i = 0; i < tasks.size(); i++) { - ActivityManager.RunningTaskInfo task = tasks.get(i); - int stackId = task.stackId; - if (stackId != RECENTS_STACK_ID && stackId != PINNED_STACK_ID) { - return task; - } - } - } - return null; - } - - /** * Returns whether the recents activity is currently visible. */ public boolean isRecentsActivityVisible() { @@ -512,6 +196,8 @@ public class SystemServicesProxy { * * @param isHomeStackVisible if provided, will return whether the home stack is visible * regardless of the recents visibility + * + * TODO(winsonc): Refactor this check to just use the recents activity lifecycle */ public boolean isRecentsActivityVisible(MutableBoolean isHomeStackVisible) { if (mIam == null) return false; @@ -522,12 +208,17 @@ public class SystemServicesProxy { ActivityManager.StackInfo fullscreenStackInfo = null; ActivityManager.StackInfo recentsStackInfo = null; for (int i = 0; i < stackInfos.size(); i++) { - StackInfo stackInfo = stackInfos.get(i); - if (stackInfo.stackId == HOME_STACK_ID) { + final StackInfo stackInfo = stackInfos.get(i); + final WindowConfiguration winConfig = stackInfo.configuration.windowConfiguration; + final int activityType = winConfig.getActivityType(); + final int windowingMode = winConfig.getWindowingMode(); + if (homeStackInfo == null && activityType == ACTIVITY_TYPE_HOME) { homeStackInfo = stackInfo; - } else if (stackInfo.stackId == FULLSCREEN_WORKSPACE_STACK_ID) { + } else if (fullscreenStackInfo == null && activityType == ACTIVITY_TYPE_STANDARD + && (windowingMode == WINDOWING_MODE_FULLSCREEN + || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) { fullscreenStackInfo = stackInfo; - } else if (stackInfo.stackId == RECENTS_STACK_ID) { + } else if (recentsStackInfo == null && activityType == ACTIVITY_TYPE_RECENTS) { recentsStackInfo = stackInfo; } } @@ -561,76 +252,34 @@ public class SystemServicesProxy { } /** - * Returns whether this device has freeform workspaces. - */ - public boolean hasFreeformWorkspaceSupport() { - return mHasFreeformWorkspaceSupport; - } - - /** * Returns whether this device is in the safe mode. */ public boolean isInSafeMode() { return mIsSafeMode; } - /** Docks a task to the side of the screen and starts it. */ - public boolean startTaskInDockedMode(int taskId, int createMode) { - if (mIam == null) return false; - - try { - final ActivityOptions options = ActivityOptions.makeBasic(); - options.setDockCreateMode(createMode); - options.setLaunchStackId(DOCKED_STACK_ID); - mIam.startActivityFromRecents(taskId, options.toBundle()); - return true; - } catch (Exception e) { - Log.e(TAG, "Failed to dock task: " + taskId + " with createMode: " + createMode, e); - } - return false; - } - - /** Docks an already resumed task to the side of the screen. */ - public boolean moveTaskToDockedStack(int taskId, int createMode, Rect initialBounds) { + /** Moves an already resumed task to the side of the screen to initiate split screen. */ + public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, + Rect initialBounds) { if (mIam == null) { return false; } try { - return mIam.moveTaskToDockedStack(taskId, createMode, true /* onTop */, - false /* animate */, initialBounds); + return mIam.setTaskWindowingModeSplitScreenPrimary(taskId, createMode, true /* onTop */, + false /* animate */, initialBounds, true /* showRecents */); } catch (RemoteException e) { e.printStackTrace(); } return false; } - /** - * Returns whether the given stack id is the home stack id. - */ - public static boolean isHomeStack(int stackId) { - return stackId == HOME_STACK_ID; - } - - /** - * Returns whether the given stack id is the pinned stack id. - */ - public static boolean isPinnedStack(int stackId){ - return stackId == PINNED_STACK_ID; - } - - /** - * Returns whether the given stack id is the docked stack id. - */ - public static boolean isDockedStack(int stackId) { - return stackId == DOCKED_STACK_ID; - } - - /** - * Returns whether the given stack id is the freeform workspace stack id. - */ - public static boolean isFreeformStack(int stackId) { - return stackId == FREEFORM_WORKSPACE_STACK_ID; + public ActivityManager.StackInfo getSplitScreenPrimaryStack() { + try { + return mIam.getStackInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED); + } catch (RemoteException e) { + return null; + } } /** @@ -639,13 +288,7 @@ public class SystemServicesProxy { public boolean hasDockedTask() { if (mIam == null) return false; - ActivityManager.StackInfo stackInfo = null; - try { - stackInfo = mIam.getStackInfo(DOCKED_STACK_ID); - } catch (RemoteException e) { - e.printStackTrace(); - } - + ActivityManager.StackInfo stackInfo = getSplitScreenPrimaryStack(); if (stackInfo != null) { int userId = getCurrentUser(); boolean hasUserTask = false; @@ -662,7 +305,7 @@ public class SystemServicesProxy { */ public boolean hasSoftNavigationBar() { try { - return WindowManagerGlobal.getWindowManagerService().hasNavigationBar(); + return mIwm.hasNavigationBar(); } catch (RemoteException e) { e.printStackTrace(); } @@ -679,371 +322,17 @@ public class SystemServicesProxy { return insets.right > 0; } - /** - * Cancels the current window transtion to/from Recents for the given task id. - */ - public void cancelWindowTransition(int taskId) { + /** Set the task's windowing mode. */ + public void setTaskWindowingMode(int taskId, int windowingMode) { if (mIam == null) return; try { - mIam.cancelTaskWindowTransition(taskId); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - /** - * Cancels the current thumbnail transtion to/from Recents for the given task id. - */ - public void cancelThumbnailTransition(int taskId) { - if (mIam == null) return; - - try { - mIam.cancelTaskThumbnailTransition(taskId); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - /** Returns the top task thumbnail for the given task id */ - public ThumbnailData getTaskThumbnail(int taskId, boolean reduced) { - if (mAm == null) return null; - - // If we are mocking, then just return a dummy thumbnail - if (RecentsDebugFlags.Static.EnableMockTasks) { - ThumbnailData thumbnailData = new ThumbnailData(); - thumbnailData.thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth, - mDummyThumbnailHeight, Bitmap.Config.ARGB_8888); - thumbnailData.thumbnail.eraseColor(0xff333333); - return thumbnailData; - } - - ThumbnailData thumbnailData = getThumbnail(taskId, reduced); - if (thumbnailData.thumbnail != null && !ActivityManager.ENABLE_TASK_SNAPSHOTS) { - thumbnailData.thumbnail.setHasAlpha(false); - // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top - // left pixel, then assume the whole thumbnail is transparent. Generally, proper - // screenshots are always composed onto a bitmap that has no alpha. - if (Color.alpha(thumbnailData.thumbnail.getPixel(0, 0)) == 0) { - mBgProtectionCanvas.setBitmap(thumbnailData.thumbnail); - mBgProtectionCanvas.drawRect(0, 0, thumbnailData.thumbnail.getWidth(), - thumbnailData.thumbnail.getHeight(), mBgProtectionPaint); - mBgProtectionCanvas.setBitmap(null); - Log.e(TAG, "Invalid screenshot detected from getTaskThumbnail()"); - } - } - return thumbnailData; - } - - /** - * Returns a task thumbnail from the activity manager - */ - public @NonNull ThumbnailData getThumbnail(int taskId, boolean reducedResolution) { - if (mAm == null) { - return new ThumbnailData(); - } - - final ThumbnailData thumbnailData; - if (ActivityManager.ENABLE_TASK_SNAPSHOTS) { - ActivityManager.TaskSnapshot snapshot = null; - try { - snapshot = ActivityManager.getService().getTaskSnapshot(taskId, reducedResolution); - } catch (RemoteException e) { - Log.w(TAG, "Failed to retrieve snapshot", e); - } - if (snapshot != null) { - thumbnailData = ThumbnailData.createFromTaskSnapshot(snapshot); - } else { - return new ThumbnailData(); - } - } else { - ActivityManager.TaskThumbnail taskThumbnail = mAm.getTaskThumbnail(taskId); - if (taskThumbnail == null) { - return new ThumbnailData(); - } - - Bitmap thumbnail = taskThumbnail.mainThumbnail; - ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor; - if (thumbnail == null && descriptor != null) { - thumbnail = BitmapFactory.decodeFileDescriptor(descriptor.getFileDescriptor(), - null, sBitmapOptions); - } - if (descriptor != null) { - try { - descriptor.close(); - } catch (IOException e) { - } - } - thumbnailData = new ThumbnailData(); - thumbnailData.thumbnail = thumbnail; - thumbnailData.orientation = taskThumbnail.thumbnailInfo.screenOrientation; - thumbnailData.insets.setEmpty(); - } - return thumbnailData; - } - - /** - * Moves a task into another stack. - */ - public void moveTaskToStack(int taskId, int stackId) { - if (mIam == null) return; - - try { - mIam.positionTaskInStack(taskId, stackId, 0); + mIam.setTaskWindowingMode(taskId, windowingMode, false /* onTop */); } catch (RemoteException | IllegalArgumentException e) { e.printStackTrace(); } } - /** Removes the task */ - public void removeTask(final int taskId) { - if (mAm == null) return; - if (RecentsDebugFlags.Static.EnableMockTasks) return; - - // Remove the task. - mUiOffloadThread.submit(() -> { - mAm.removeTask(taskId); - }); - } - - /** - * Sends a message to close other system windows. - */ - public void sendCloseSystemWindows(String reason) { - mUiOffloadThread.submit(() -> { - try { - mIam.closeSystemDialogs(reason); - } catch (RemoteException e) { - } - }); - } - - /** - * Returns the activity info for a given component name. - * - * @param cn The component name of the activity. - * @param userId The userId of the user that this is for. - */ - public ActivityInfo getActivityInfo(ComponentName cn, int userId) { - if (mIpm == null) return null; - if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo(); - - try { - return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId); - } catch (RemoteException e) { - e.printStackTrace(); - return null; - } - } - - /** - * Returns the activity info for a given component name. - * - * @param cn The component name of the activity. - */ - public ActivityInfo getActivityInfo(ComponentName cn) { - if (mPm == null) return null; - if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo(); - - try { - return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - return null; - } - } - - /** - * Returns the activity label, badging if necessary. - */ - public String getBadgedActivityLabel(ActivityInfo info, int userId) { - if (mPm == null) return null; - - // If we are mocking, then return a mock label - if (RecentsDebugFlags.Static.EnableMockTasks) { - return "Recent Task: " + userId; - } - - return getBadgedLabel(info.loadLabel(mPm).toString(), userId); - } - - /** - * Returns the application label, badging if necessary. - */ - public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) { - if (mPm == null) return null; - - // If we are mocking, then return a mock label - if (RecentsDebugFlags.Static.EnableMockTasks) { - return "Recent Task App: " + userId; - } - - return getBadgedLabel(appInfo.loadLabel(mPm).toString(), userId); - } - - /** - * Returns the content description for a given task, badging it if necessary. The content - * description joins the app and activity labels. - */ - public String getBadgedContentDescription(ActivityInfo info, int userId, - ActivityManager.TaskDescription td, Resources res) { - // If we are mocking, then return a mock label - if (RecentsDebugFlags.Static.EnableMockTasks) { - return "Recent Task Content Description: " + userId; - } - - String activityLabel; - if (td != null && td.getLabel() != null) { - activityLabel = td.getLabel(); - } else { - activityLabel = info.loadLabel(mPm).toString(); - } - String applicationLabel = info.applicationInfo.loadLabel(mPm).toString(); - String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId); - return applicationLabel.equals(activityLabel) ? badgedApplicationLabel - : res.getString(R.string.accessibility_recents_task_header, - badgedApplicationLabel, activityLabel); - } - - /** - * Returns the activity icon for the ActivityInfo for a user, badging if - * necessary. - */ - public Drawable getBadgedActivityIcon(ActivityInfo info, int userId) { - if (mPm == null) return null; - - // If we are mocking, then return a mock label - if (RecentsDebugFlags.Static.EnableMockTasks) { - return new ColorDrawable(0xFF666666); - } - - return mDrawableFactory.getBadgedIcon(info, info.applicationInfo, userId); - } - - /** - * Returns the application icon for the ApplicationInfo for a user, badging if - * necessary. - */ - public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) { - if (mPm == null) return null; - - // If we are mocking, then return a mock label - if (RecentsDebugFlags.Static.EnableMockTasks) { - return new ColorDrawable(0xFF666666); - } - - return mDrawableFactory.getBadgedIcon(appInfo, userId); - } - - /** - * Returns the task description icon, loading and badging it if it necessary. - */ - public Drawable getBadgedTaskDescriptionIcon(ActivityManager.TaskDescription taskDescription, - int userId, Resources res) { - - // If we are mocking, then return a mock label - if (RecentsDebugFlags.Static.EnableMockTasks) { - return new ColorDrawable(0xFF666666); - } - - Bitmap tdIcon = taskDescription.getInMemoryIcon(); - if (tdIcon == null) { - tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon( - taskDescription.getIconFilename(), userId); - } - if (tdIcon != null) { - return getBadgedIcon(new BitmapDrawable(res, tdIcon), userId); - } - return null; - } - - public ActivityManager.TaskDescription getTaskDescription(int taskId) { - try { - return mIam.getTaskDescription(taskId); - } catch (RemoteException e) { - return null; - } - } - - /** - * Returns the given icon for a user, badging if necessary. - */ - private Drawable getBadgedIcon(Drawable icon, int userId) { - if (userId != UserHandle.myUserId()) { - icon = mPm.getUserBadgedIcon(icon, new UserHandle(userId)); - } - return icon; - } - - /** - * Returns a banner used on TV for the specified Activity. - */ - public Drawable getActivityBanner(ActivityInfo info) { - if (mPm == null) return null; - - // If we are mocking, then return a mock banner - if (RecentsDebugFlags.Static.EnableMockTasks) { - return new ColorDrawable(0xFF666666); - } - - Drawable banner = info.loadBanner(mPm); - return banner; - } - - /** - * Returns a logo used on TV for the specified Activity. - */ - public Drawable getActivityLogo(ActivityInfo info) { - if (mPm == null) return null; - - // If we are mocking, then return a mock logo - if (RecentsDebugFlags.Static.EnableMockTasks) { - return new ColorDrawable(0xFF666666); - } - - Drawable logo = info.loadLogo(mPm); - return logo; - } - - - /** - * Returns the given label for a user, badging if necessary. - */ - private String getBadgedLabel(String label, int userId) { - if (userId != UserHandle.myUserId()) { - label = mPm.getUserBadgedLabel(label, new UserHandle(userId)).toString(); - } - return label; - } - - /** - * Returns whether the provided {@param userId} is currently locked (and showing Keyguard). - */ - public boolean isDeviceLocked(int userId) { - if (mKgm == null) { - return false; - } - return mKgm.isDeviceLocked(userId); - } - - /** Returns the package name of the home activity. */ - public String getHomeActivityPackageName() { - if (mPm == null) return null; - if (RecentsDebugFlags.Static.EnableMockTasks) return null; - - ArrayList<ResolveInfo> homeActivities = new ArrayList<>(); - ComponentName defaultHomeActivity = mPm.getHomeActivities(homeActivities); - if (defaultHomeActivity != null) { - return defaultHomeActivity.getPackageName(); - } else if (homeActivities.size() == 1) { - ResolveInfo info = homeActivities.get(0); - if (info.activityInfo != null) { - return info.activityInfo.packageName; - } - } - return null; - } - /** * Returns whether the provided {@param userId} represents the system user. */ @@ -1083,36 +372,13 @@ public class SystemServicesProxy { if (mIam == null) return false; try { - return mIam.isInLockTaskMode(); + return mIam.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED; } catch (RemoteException e) { return false; } } /** - * Returns a global setting. - */ - public int getGlobalSetting(Context context, String setting) { - ContentResolver cr = context.getContentResolver(); - return Settings.Global.getInt(cr, setting, 0); - } - - /** - * Returns a system setting. - */ - public int getSystemSetting(Context context, String setting) { - ContentResolver cr = context.getContentResolver(); - return Settings.System.getInt(cr, setting, 0); - } - - /** - * Returns a system property. - */ - public String getSystemProperty(String key) { - return SystemProperties.get(key); - } - - /** * Returns the smallest width/height. */ public int getDeviceSmallestWidth() { @@ -1146,9 +412,10 @@ public class SystemServicesProxy { try { // Use the recents stack bounds, fallback to fullscreen stack if it is null - ActivityManager.StackInfo stackInfo = mIam.getStackInfo(RECENTS_STACK_ID); + ActivityManager.StackInfo stackInfo = + mIam.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS); if (stackInfo == null) { - stackInfo = mIam.getStackInfo(FULLSCREEN_WORKSPACE_STACK_ID); + stackInfo = mIam.getStackInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); } if (stackInfo != null) { windowRect.set(stackInfo.bounds); @@ -1165,47 +432,6 @@ public class SystemServicesProxy { opts != null ? opts.toBundle() : null, UserHandle.CURRENT)); } - /** Starts an activity from recents. */ - public void startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName, - ActivityOptions options, int stackId, - @Nullable final StartActivityFromRecentsResultListener resultListener) { - if (mIam == null) { - return; - } - if (taskKey.stackId == DOCKED_STACK_ID) { - // We show non-visible docked tasks in Recents, but we always want to launch - // them in the fullscreen stack. - if (options == null) { - options = ActivityOptions.makeBasic(); - } - options.setLaunchStackId(FULLSCREEN_WORKSPACE_STACK_ID); - } else if (stackId != INVALID_STACK_ID) { - if (options == null) { - options = ActivityOptions.makeBasic(); - } - options.setLaunchStackId(stackId); - } - final ActivityOptions finalOptions = options; - - // Execute this from another thread such that we can do other things (like caching the - // bitmap for the thumbnail) while AM is busy starting our activity. - mUiOffloadThread.submit(() -> { - try { - mIam.startActivityFromRecents( - taskKey.id, finalOptions == null ? null : finalOptions.toBundle()); - if (resultListener != null) { - mHandler.post(() -> resultListener.onStartActivityResult(true)); - } - } catch (Exception e) { - Log.e(TAG, context.getString( - R.string.recents_launch_error_message, taskName), e); - if (resultListener != null) { - mHandler.post(() -> resultListener.onStartActivityResult(false)); - } - } - }); - } - /** Starts an in-place animation on the front most application windows. */ public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) { if (mIam == null) return; @@ -1218,32 +444,12 @@ public class SystemServicesProxy { } } - /** - * Registers a task stack listener with the system. - * This should be called on the main thread. - */ - public void registerTaskStackListener(TaskStackListener listener) { - if (mIam == null) return; - - synchronized (mTaskStackListeners) { - mTaskStackListeners.add(listener); - if (mTaskStackListeners.size() == 1) { - // Register mTaskStackListener to IActivityManager only once if needed. - try { - mIam.registerTaskStackListener(mTaskStackListener); - } catch (Exception e) { - Log.w(TAG, "Failed to call registerTaskStackListener", e); - } - } - } - } - public void endProlongedAnimations() { if (mWm == null) { return; } try { - WindowManagerGlobal.getWindowManagerService().endProlongedAnimations(); + mIwm.endProlongedAnimations(); } catch (Exception e) { e.printStackTrace(); } @@ -1253,7 +459,7 @@ public class SystemServicesProxy { if (mWm == null) return; try { - WindowManagerGlobal.getWindowManagerService().registerDockedStackListener(listener); + mIwm.registerDockedStackListener(listener); } catch (Exception e) { e.printStackTrace(); } @@ -1280,45 +486,36 @@ public class SystemServicesProxy { if (mWm == null) return; try { - WindowManagerGlobal.getWindowManagerService().getStableInsets(Display.DEFAULT_DISPLAY, - outStableInsets); + mIwm.getStableInsets(Display.DEFAULT_DISPLAY, outStableInsets); } catch (Exception e) { e.printStackTrace(); } } - public void overridePendingAppTransitionMultiThumbFuture( - IAppTransitionAnimationSpecsFuture future, IRemoteCallback animStartedListener, - boolean scaleUp) { - try { - WindowManagerGlobal.getWindowManagerService() - .overridePendingAppTransitionMultiThumbFuture(future, animStartedListener, - scaleUp); - } catch (RemoteException e) { - Log.w(TAG, "Failed to override transition: " + e); - } - } - /** * Updates the visibility of recents. */ - public void setRecentsVisibility(boolean visible) { - try { - mIwm.setRecentsVisibility(visible); - } catch (RemoteException e) { - Log.e(TAG, "Unable to reach window manager", e); - } + public void setRecentsVisibility(final boolean visible) { + mUiOffloadThread.submit(() -> { + try { + mIwm.setRecentsVisibility(visible); + } catch (RemoteException e) { + Log.e(TAG, "Unable to reach window manager", e); + } + }); } /** * Updates the visibility of the picture-in-picture. */ - public void setPipVisibility(boolean visible) { - try { - mIwm.setPipVisibility(visible); - } catch (RemoteException e) { - Log.e(TAG, "Unable to reach window manager", e); - } + public void setPipVisibility(final boolean visible) { + mUiOffloadThread.submit(() -> { + try { + mIwm.setPipVisibility(visible); + } catch (RemoteException e) { + Log.e(TAG, "Unable to reach window manager", e); + } + }); } public boolean isDreaming() { @@ -1340,111 +537,7 @@ public class SystemServicesProxy { }); } - public void updateOverviewLastStackActiveTimeAsync(long newLastStackActiveTime, - int currentUserId) { - mUiOffloadThread.submit(() -> { - Settings.Secure.putLongForUser(mContext.getContentResolver(), - Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, newLastStackActiveTime, currentUserId); - }); - } - public interface StartActivityFromRecentsResultListener { void onStartActivityResult(boolean succeeded); } - - private final class H extends Handler { - private static final int ON_TASK_STACK_CHANGED = 1; - private static final int ON_TASK_SNAPSHOT_CHANGED = 2; - private static final int ON_ACTIVITY_PINNED = 3; - private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 4; - private static final int ON_PINNED_STACK_ANIMATION_ENDED = 5; - private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6; - private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7; - private static final int ON_TASK_PROFILE_LOCKED = 8; - private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9; - private static final int ON_ACTIVITY_UNPINNED = 10; - private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED = 11; - - @Override - public void handleMessage(Message msg) { - synchronized (mTaskStackListeners) { - switch (msg.what) { - case ON_TASK_STACK_CHANGED: { - Trace.beginSection("onTaskStackChanged"); - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onTaskStackChanged(); - } - Trace.endSection(); - break; - } - case ON_TASK_SNAPSHOT_CHANGED: { - Trace.beginSection("onTaskSnapshotChanged"); - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1, - (TaskSnapshot) msg.obj); - } - Trace.endSection(); - break; - } - case ON_ACTIVITY_PINNED: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onActivityPinned((String) msg.obj, msg.arg1, - msg.arg2); - } - break; - } - case ON_ACTIVITY_UNPINNED: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onActivityUnpinned(); - } - break; - } - case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onPinnedActivityRestartAttempt( - msg.arg1 != 0); - } - break; - } - case ON_PINNED_STACK_ANIMATION_STARTED: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onPinnedStackAnimationStarted(); - } - break; - } - case ON_PINNED_STACK_ANIMATION_ENDED: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onPinnedStackAnimationEnded(); - } - break; - } - case ON_ACTIVITY_FORCED_RESIZABLE: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onActivityForcedResizable( - (String) msg.obj, msg.arg1, msg.arg2); - } - break; - } - case ON_ACTIVITY_DISMISSING_DOCKED_STACK: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onActivityDismissingDockedStack(); - } - break; - } - case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onActivityLaunchOnSecondaryDisplayFailed(); - } - break; - } - case ON_TASK_PROFILE_LOCKED: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2); - } - break; - } - } - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java deleted file mode 100644 index 4349e30f60e0..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.misc; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.RectEvaluator; -import android.annotation.FloatRange; -import android.app.Activity; -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Color; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.Drawable; -import android.os.Trace; -import android.util.ArraySet; -import android.util.IntProperty; -import android.util.Property; -import android.util.TypedValue; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.ViewStub; - -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.views.TaskViewTransform; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/* Common code */ -public class Utilities { - - public static final Property<Drawable, Integer> DRAWABLE_ALPHA = - new IntProperty<Drawable>("drawableAlpha") { - @Override - public void setValue(Drawable object, int alpha) { - object.setAlpha(alpha); - } - - @Override - public Integer get(Drawable object) { - return object.getAlpha(); - } - }; - - public static final Property<Drawable, Rect> DRAWABLE_RECT = - new Property<Drawable, Rect>(Rect.class, "drawableBounds") { - @Override - public void set(Drawable object, Rect bounds) { - object.setBounds(bounds); - } - - @Override - public Rect get(Drawable object) { - return object.getBounds(); - } - }; - - public static final RectFEvaluator RECTF_EVALUATOR = new RectFEvaluator(); - public static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect()); - public static final Rect EMPTY_RECT = new Rect(); - - /** - * @return the first parent walking up the view hierarchy that has the given class type. - * - * @param parentClass must be a class derived from {@link View} - */ - public static <T extends View> T findParent(View v, Class<T> parentClass) { - ViewParent parent = v.getParent(); - while (parent != null) { - if (parentClass.isAssignableFrom(parent.getClass())) { - return (T) parent; - } - parent = parent.getParent(); - } - return null; - } - - /** - * Initializes the {@param setOut} with the given object. - */ - public static <T> ArraySet<T> objectToSet(T obj, ArraySet<T> setOut) { - setOut.clear(); - if (obj != null) { - setOut.add(obj); - } - return setOut; - } - - /** - * Replaces the contents of {@param setOut} with the contents of the {@param array}. - */ - public static <T> ArraySet<T> arrayToSet(T[] array, ArraySet<T> setOut) { - setOut.clear(); - if (array != null) { - Collections.addAll(setOut, array); - } - return setOut; - } - - /** - * @return the clamped {@param value} between the provided {@param min} and {@param max}. - */ - public static float clamp(float value, float min, float max) { - return Math.max(min, Math.min(max, value)); - } - - /** - * @return the clamped {@param value} between the provided {@param min} and {@param max}. - */ - public static int clamp(int value, int min, int max) { - return Math.max(min, Math.min(max, value)); - } - - /** - * @return the clamped {@param value} between 0 and 1. - */ - public static float clamp01(float value) { - return Math.max(0f, Math.min(1f, value)); - } - - /** - * Scales the {@param value} to be proportionally between the {@param min} and - * {@param max} values. - * - * @param value must be between 0 and 1 - */ - public static float mapRange(@FloatRange(from=0.0,to=1.0) float value, float min, float max) { - return min + (value * (max - min)); - } - - /** - * Scales the {@param value} proportionally from {@param min} and {@param max} to 0 and 1. - * - * @param value must be between {@param min} and {@param max} - */ - public static float unmapRange(float value, float min, float max) { - return (value - min) / (max - min); - } - - /** Scales a rect about its centroid */ - public static void scaleRectAboutCenter(RectF r, float scale) { - if (scale != 1.0f) { - float cx = r.centerX(); - float cy = r.centerY(); - r.offset(-cx, -cy); - r.left *= scale; - r.top *= scale; - r.right *= scale; - r.bottom *= scale; - r.offset(cx, cy); - } - } - - /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */ - public static float computeContrastBetweenColors(int bg, int fg) { - float bgR = Color.red(bg) / 255f; - float bgG = Color.green(bg) / 255f; - float bgB = Color.blue(bg) / 255f; - bgR = (bgR < 0.03928f) ? bgR / 12.92f : (float) Math.pow((bgR + 0.055f) / 1.055f, 2.4f); - bgG = (bgG < 0.03928f) ? bgG / 12.92f : (float) Math.pow((bgG + 0.055f) / 1.055f, 2.4f); - bgB = (bgB < 0.03928f) ? bgB / 12.92f : (float) Math.pow((bgB + 0.055f) / 1.055f, 2.4f); - float bgL = 0.2126f * bgR + 0.7152f * bgG + 0.0722f * bgB; - - float fgR = Color.red(fg) / 255f; - float fgG = Color.green(fg) / 255f; - float fgB = Color.blue(fg) / 255f; - fgR = (fgR < 0.03928f) ? fgR / 12.92f : (float) Math.pow((fgR + 0.055f) / 1.055f, 2.4f); - fgG = (fgG < 0.03928f) ? fgG / 12.92f : (float) Math.pow((fgG + 0.055f) / 1.055f, 2.4f); - fgB = (fgB < 0.03928f) ? fgB / 12.92f : (float) Math.pow((fgB + 0.055f) / 1.055f, 2.4f); - float fgL = 0.2126f * fgR + 0.7152f * fgG + 0.0722f * fgB; - - return Math.abs((fgL + 0.05f) / (bgL + 0.05f)); - } - - /** Returns the base color overlaid with another overlay color with a specified alpha. */ - public static int getColorWithOverlay(int baseColor, int overlayColor, float overlayAlpha) { - return Color.rgb( - (int) (overlayAlpha * Color.red(baseColor) + - (1f - overlayAlpha) * Color.red(overlayColor)), - (int) (overlayAlpha * Color.green(baseColor) + - (1f - overlayAlpha) * Color.green(overlayColor)), - (int) (overlayAlpha * Color.blue(baseColor) + - (1f - overlayAlpha) * Color.blue(overlayColor))); - } - - /** - * Cancels an animation ensuring that if it has listeners, onCancel and onEnd - * are not called. - */ - public static void cancelAnimationWithoutCallbacks(Animator animator) { - if (animator != null && animator.isStarted()) { - removeAnimationListenersRecursive(animator); - animator.cancel(); - } - } - - /** - * Recursively removes all the listeners of all children of this animator - */ - public static void removeAnimationListenersRecursive(Animator animator) { - if (animator instanceof AnimatorSet) { - ArrayList<Animator> animators = ((AnimatorSet) animator).getChildAnimations(); - for (int i = animators.size() - 1; i >= 0; i--) { - removeAnimationListenersRecursive(animators.get(i)); - } - } - animator.removeAllListeners(); - } - - /** - * Sets the given {@link View}'s frame from its current translation. - */ - public static void setViewFrameFromTranslation(View v) { - RectF taskViewRect = new RectF(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); - taskViewRect.offset(v.getTranslationX(), v.getTranslationY()); - v.setTranslationX(0); - v.setTranslationY(0); - v.setLeftTopRightBottom((int) taskViewRect.left, (int) taskViewRect.top, - (int) taskViewRect.right, (int) taskViewRect.bottom); - } - - /** - * Returns a view stub for the given view id. - */ - public static ViewStub findViewStubById(View v, int stubId) { - return (ViewStub) v.findViewById(stubId); - } - - /** - * Returns a view stub for the given view id. - */ - public static ViewStub findViewStubById(Activity a, int stubId) { - return (ViewStub) a.findViewById(stubId); - } - - /** - * Updates {@param transforms} to be the same size as {@param tasks}. - */ - public static void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) { - // We can reuse the task transforms where possible to reduce object allocation - int taskTransformCount = transforms.size(); - int taskCount = tasks.size(); - if (taskTransformCount < taskCount) { - // If there are less transforms than tasks, then add as many transforms as necessary - for (int i = taskTransformCount; i < taskCount; i++) { - transforms.add(new TaskViewTransform()); - } - } else if (taskTransformCount > taskCount) { - // If there are more transforms than tasks, then just subset the transform list - transforms.subList(taskCount, taskTransformCount).clear(); - } - } - - /** - * Used for debugging, converts DP to PX. - */ - public static float dpToPx(Resources res, float dp) { - return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, res.getDisplayMetrics()); - } - - /** - * Adds a trace event for debugging. - */ - public static void addTraceEvent(String event) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, event); - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - } - - /** - * Returns whether this view, or one of its descendants have accessibility focus. - */ - public static boolean isDescendentAccessibilityFocused(View v) { - if (v.isAccessibilityFocused()) { - return true; - } - - if (v instanceof ViewGroup) { - ViewGroup vg = (ViewGroup) v; - int childCount = vg.getChildCount(); - for (int i = 0; i < childCount; i++) { - if (isDescendentAccessibilityFocused(vg.getChildAt(i))) { - return true; - } - } - } - return false; - } - - /** - * Returns the application configuration, which is independent of the activity's current - * configuration in multiwindow. - */ - public static Configuration getAppConfiguration(Context context) { - return context.getApplicationContext().getResources().getConfiguration(); - } - - /** - * Returns a lightweight dump of a rect. - */ - public static String dumpRect(Rect r) { - if (r == null) { - return "N:0,0-0,0"; - } - return r.left + "," + r.top + "-" + r.right + "," + r.bottom; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java deleted file mode 100644 index 48fa6c3c053b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java +++ /dev/null @@ -1,235 +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 com.android.systemui.recents.model; - -import static android.os.Process.setThreadPriority; - -import android.os.Handler; -import android.os.Looper; -import android.os.SystemClock; -import android.util.ArraySet; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.RecentsConfiguration; -import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.model.Task.TaskCallbacks; - -import java.util.ArrayDeque; -import java.util.ArrayList; - -/** - * Loader class that loads full-resolution thumbnails when appropriate. - */ -public class HighResThumbnailLoader implements TaskCallbacks { - - @GuardedBy("mLoadQueue") - private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>(); - @GuardedBy("mLoadQueue") - private final ArraySet<Task> mLoadingTasks = new ArraySet<>(); - @GuardedBy("mLoadQueue") - private boolean mLoaderIdling; - - private final ArrayList<Task> mVisibleTasks = new ArrayList<>(); - private final Thread mLoadThread; - private final Handler mMainThreadHandler; - private final SystemServicesProxy mSystemServicesProxy; - private final boolean mIsLowRamDevice; - private boolean mLoading; - private boolean mVisible; - private boolean mFlingingFast; - private boolean mTaskLoadQueueIdle; - - public HighResThumbnailLoader(SystemServicesProxy ssp, Looper looper, boolean isLowRamDevice) { - mMainThreadHandler = new Handler(looper); - mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader"); - mLoadThread.start(); - mSystemServicesProxy = ssp; - mIsLowRamDevice = isLowRamDevice; - } - - public void setVisible(boolean visible) { - if (mIsLowRamDevice) { - return; - } - mVisible = visible; - updateLoading(); - } - - public void setFlingingFast(boolean flingingFast) { - if (mFlingingFast == flingingFast || mIsLowRamDevice) { - return; - } - mFlingingFast = flingingFast; - updateLoading(); - } - - /** - * Sets whether the other task load queue is idling. Avoid double-loading bitmaps by not - * starting this queue until the other queue is idling. - */ - public void setTaskLoadQueueIdle(boolean idle) { - if (mIsLowRamDevice) { - return; - } - mTaskLoadQueueIdle = idle; - updateLoading(); - } - - @VisibleForTesting - boolean isLoading() { - return mLoading; - } - - private void updateLoading() { - setLoading(mVisible && !mFlingingFast && mTaskLoadQueueIdle); - } - - private void setLoading(boolean loading) { - if (loading == mLoading) { - return; - } - synchronized (mLoadQueue) { - mLoading = loading; - if (!loading) { - stopLoading(); - } else { - startLoading(); - } - } - } - - @GuardedBy("mLoadQueue") - private void startLoading() { - for (int i = mVisibleTasks.size() - 1; i >= 0; i--) { - Task t = mVisibleTasks.get(i); - if ((t.thumbnail == null || t.thumbnail.reducedResolution) - && !mLoadQueue.contains(t) && !mLoadingTasks.contains(t)) { - mLoadQueue.add(t); - } - } - mLoadQueue.notifyAll(); - } - - @GuardedBy("mLoadQueue") - private void stopLoading() { - mLoadQueue.clear(); - mLoadQueue.notifyAll(); - } - - /** - * Needs to be called when a task becomes visible. Note that this is different from - * {@link TaskCallbacks#onTaskDataLoaded} as this method should only be called once when it - * becomes visible, whereas onTaskDataLoaded can be called multiple times whenever some data - * has been updated. - */ - public void onTaskVisible(Task t) { - t.addCallback(this); - mVisibleTasks.add(t); - if ((t.thumbnail == null || t.thumbnail.reducedResolution) && mLoading) { - synchronized (mLoadQueue) { - mLoadQueue.add(t); - mLoadQueue.notifyAll(); - } - } - } - - /** - * Needs to be called when a task becomes visible. See {@link #onTaskVisible} why this is - * different from {@link TaskCallbacks#onTaskDataUnloaded()} - */ - public void onTaskInvisible(Task t) { - t.removeCallback(this); - mVisibleTasks.remove(t); - synchronized (mLoadQueue) { - mLoadQueue.remove(t); - } - } - - @VisibleForTesting - void waitForLoaderIdle() { - while (true) { - synchronized (mLoadQueue) { - if (mLoadQueue.isEmpty() && mLoaderIdling) { - return; - } - } - SystemClock.sleep(100); - } - } - - @Override - public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) { - if (thumbnailData != null && !thumbnailData.reducedResolution) { - synchronized (mLoadQueue) { - mLoadQueue.remove(task); - } - } - } - - @Override - public void onTaskDataUnloaded() { - } - - @Override - public void onTaskStackIdChanged() { - } - - private final Runnable mLoader = new Runnable() { - - @Override - public void run() { - setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND + 1); - while (true) { - Task next = null; - synchronized (mLoadQueue) { - if (!mLoading || mLoadQueue.isEmpty()) { - try { - mLoaderIdling = true; - mLoadQueue.wait(); - mLoaderIdling = false; - } catch (InterruptedException e) { - // Don't care. - } - } else { - next = mLoadQueue.poll(); - if (next != null) { - mLoadingTasks.add(next); - } - } - } - if (next != null) { - loadTask(next); - } - } - } - - private void loadTask(Task t) { - ThumbnailData thumbnail = mSystemServicesProxy.getTaskThumbnail(t.key.id, - false /* reducedResolution */); - mMainThreadHandler.post(() -> { - synchronized (mLoadQueue) { - mLoadingTasks.remove(t); - } - if (mVisibleTasks.contains(t)) { - t.notifyTaskDataLoaded(thumbnail, t.icon); - } - }); - } - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java deleted file mode 100644 index 308cece1dfdd..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.model; - -import android.content.Context; -import android.os.UserHandle; - -import com.android.internal.content.PackageMonitor; -import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.activity.PackagesChangedEvent; -import com.android.systemui.recents.misc.ForegroundThread; - -/** - * The package monitor listens for changes from PackageManager to update the contents of the - * Recents list. - */ -public class RecentsPackageMonitor extends PackageMonitor { - - /** Registers the broadcast receivers with the specified callbacks. */ - public void register(Context context) { - try { - // We register for events from all users, but will cross-reference them with - // packages for the current user and any profiles they have. Ensure that events are - // handled in a background thread. - register(context, ForegroundThread.get().getLooper(), UserHandle.ALL, true); - } catch (IllegalStateException e) { - e.printStackTrace(); - } - } - - /** Unregisters the broadcast receivers. */ - @Override - public void unregister() { - try { - super.unregister(); - } catch (IllegalStateException e) { - e.printStackTrace(); - } - } - - @Override - public void onPackageRemoved(String packageName, int uid) { - // Notify callbacks on the main thread that a package has changed - final int eventUserId = getChangingUserId(); - EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId)); - } - - @Override - public boolean onPackageChanged(String packageName, int uid, String[] components) { - onPackageModified(packageName); - return true; - } - - @Override - public void onPackageModified(String packageName) { - // Notify callbacks on the main thread that a package has changed - final int eventUserId = getChangingUserId(); - EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId)); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java deleted file mode 100644 index 8d31730d8602..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.model; - -import android.app.ActivityManager; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.UserInfo; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.os.UserHandle; -import android.os.UserManager; -import android.provider.Settings; -import android.provider.Settings.Secure; -import android.util.ArraySet; -import android.util.SparseArray; -import android.util.SparseBooleanArray; -import android.util.SparseIntArray; - -import com.android.systemui.Prefs; -import com.android.systemui.R; -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.RecentsDebugFlags; -import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm; -import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - - -/** - * This class stores the loading state as it goes through multiple stages of loading: - * 1) preloadRawTasks() will load the raw set of recents tasks from the system - * 2) preloadPlan() will construct a new task stack with all metadata and only icons and - * thumbnails that are currently in the cache - * 3) executePlan() will actually load and fill in the icons and thumbnails according to the load - * options specified, such that we can transition into the Recents activity seamlessly - */ -public class RecentsTaskLoadPlan { - - private static int MIN_NUM_TASKS = 5; - private static int SESSION_BEGIN_TIME = 1000 /* ms/s */ * 60 /* s/min */ * 60 /* min/hr */ * - 6 /* hrs */; - - /** The set of conditions to load tasks. */ - public static class Options { - public int runningTaskId = -1; - public boolean loadIcons = true; - public boolean loadThumbnails = false; - public boolean onlyLoadForCache = false; - public boolean onlyLoadPausedActivities = false; - public int numVisibleTasks = 0; - public int numVisibleTaskThumbnails = 0; - } - - Context mContext; - - int mPreloadedUserId; - List<ActivityManager.RecentTaskInfo> mRawTasks; - TaskStack mStack; - ArraySet<Integer> mCurrentQuietProfiles = new ArraySet<Integer>(); - - /** Package level ctor */ - RecentsTaskLoadPlan(Context context) { - mContext = context; - } - - private void updateCurrentQuietProfilesCache(int currentUserId) { - mCurrentQuietProfiles.clear(); - - UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - List<UserInfo> profiles = userManager.getProfiles(currentUserId); - if (profiles != null) { - for (int i = 0; i < profiles.size(); i++) { - UserInfo user = profiles.get(i); - if (user.isManagedProfile() && user.isQuietModeEnabled()) { - mCurrentQuietProfiles.add(user.id); - } - } - } - } - - /** - * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent - * to most-recent order. - * - * Note: Do not lock, callers should synchronize on the loader before making this call. - */ - void preloadRawTasks(boolean includeFrontMostExcludedTask) { - SystemServicesProxy ssp = Recents.getSystemServices(); - int currentUserId = ssp.getCurrentUser(); - updateCurrentQuietProfilesCache(currentUserId); - mPreloadedUserId = currentUserId; - mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(), - currentUserId, includeFrontMostExcludedTask, mCurrentQuietProfiles); - - // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it - Collections.reverse(mRawTasks); - } - - /** - * Preloads the list of recent tasks from the system. After this call, the TaskStack will - * have a list of all the recent tasks with their metadata, not including icons or - * thumbnails which were not cached and have to be loaded. - * - * The tasks will be ordered by: - * - least-recent to most-recent stack tasks - * - least-recent to most-recent freeform tasks - * - * Note: Do not lock, since this can be calling back to the loader, which separately also drives - * this call (callers should synchronize on the loader before making this call). - */ - void preloadPlan(RecentsTaskLoader loader, int runningTaskId, - boolean includeFrontMostExcludedTask) { - Resources res = mContext.getResources(); - ArrayList<Task> allTasks = new ArrayList<>(); - if (mRawTasks == null) { - preloadRawTasks(includeFrontMostExcludedTask); - } - - SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>(); - SparseIntArray affiliatedTaskCounts = new SparseIntArray(); - SparseBooleanArray lockedUsers = new SparseBooleanArray(); - String dismissDescFormat = mContext.getString( - R.string.accessibility_recents_item_will_be_dismissed); - String appInfoDescFormat = mContext.getString( - R.string.accessibility_recents_item_open_app_info); - int currentUserId = mPreloadedUserId; - long legacyLastStackActiveTime = migrateLegacyLastStackActiveTime(currentUserId); - long lastStackActiveTime = Settings.Secure.getLongForUser(mContext.getContentResolver(), - Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, legacyLastStackActiveTime, currentUserId); - if (RecentsDebugFlags.Static.EnableMockTasks) { - lastStackActiveTime = 0; - } - long newLastStackActiveTime = -1; - int taskCount = mRawTasks.size(); - for (int i = 0; i < taskCount; i++) { - ActivityManager.RecentTaskInfo t = mRawTasks.get(i); - - // Compose the task key - Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent, - t.userId, t.firstActiveTime, t.lastActiveTime); - - // This task is only shown in the stack if it satisfies the historical time or min - // number of tasks constraints. Freeform tasks are also always shown. - boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId); - boolean isStackTask; - if (Recents.getConfiguration().isGridEnabled) { - // When grid layout is enabled, we only show the first - // TaskGridLayoutAlgorithm.MAX_LAYOUT_FROM_HOME_TASK_COUNT} tasks. - isStackTask = t.lastActiveTime >= lastStackActiveTime && - i >= taskCount - TaskGridLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT; - } else if (Recents.getConfiguration().isLowRamDevice) { - // Show a max of 3 items - isStackTask = t.lastActiveTime >= lastStackActiveTime && - i >= taskCount - TaskStackLowRamLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT; - } else { - isStackTask = isFreeformTask || !isHistoricalTask(t) || - (t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS)); - } - boolean isLaunchTarget = taskKey.id == runningTaskId; - - // The last stack active time is the baseline for which we show visible tasks. Since - // the system will store all the tasks, we don't want to show the tasks prior to the - // last visible ones, otherwise, as you dismiss them, the previous tasks may satisfy - // the other stack-task constraints. - if (isStackTask && newLastStackActiveTime < 0) { - newLastStackActiveTime = t.lastActiveTime; - } - - // Load the title, icon, and color - ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey); - String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription); - String titleDescription = loader.getAndUpdateContentDescription(taskKey, - t.taskDescription, res); - String dismissDescription = String.format(dismissDescFormat, titleDescription); - String appInfoDescription = String.format(appInfoDescFormat, titleDescription); - Drawable icon = isStackTask - ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false) - : null; - ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey, - false /* loadIfNotCached */, false /* storeInCache */); - int activityColor = loader.getActivityPrimaryColor(t.taskDescription); - int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription); - boolean isSystemApp = (info != null) && - ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); - if (lockedUsers.indexOfKey(t.userId) < 0) { - lockedUsers.put(t.userId, Recents.getSystemServices().isDeviceLocked(t.userId)); - } - boolean isLocked = lockedUsers.get(t.userId); - - // Add the task to the stack - Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon, - thumbnail, title, titleDescription, dismissDescription, appInfoDescription, - activityColor, backgroundColor, isLaunchTarget, isStackTask, isSystemApp, - t.supportsSplitScreenMultiWindow, t.bounds, t.taskDescription, t.resizeMode, t.topActivity, - isLocked); - - allTasks.add(task); - affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1); - affiliatedTasks.put(taskKey.id, taskKey); - } - if (newLastStackActiveTime != -1) { - Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync( - newLastStackActiveTime, currentUserId); - } - - // Initialize the stacks - mStack = new TaskStack(); - mStack.setTasks(mContext, allTasks, false /* notifyStackChanges */); - } - - /** - * Called to apply the actual loading based on the specified conditions. - * - * Note: Do not lock, since this can be calling back to the loader, which separately also drives - * this call (callers should synchronize on the loader before making this call). - */ - void executePlan(Options opts, RecentsTaskLoader loader) { - Resources res = mContext.getResources(); - - // Iterate through each of the tasks and load them according to the load conditions. - ArrayList<Task> tasks = mStack.getStackTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - Task.TaskKey taskKey = task.key; - - boolean isRunningTask = (task.key.id == opts.runningTaskId); - boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks); - boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails); - - // If requested, skip the running task - if (opts.onlyLoadPausedActivities && isRunningTask) { - continue; - } - - if (opts.loadIcons && (isRunningTask || isVisibleTask)) { - if (task.icon == null) { - task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription, res, - true); - } - } - if (opts.loadThumbnails && isVisibleThumbnail) { - task.thumbnail = loader.getAndUpdateThumbnail(taskKey, - true /* loadIfNotCached */, true /* storeInCache */); - } - } - } - - /** - * Returns the TaskStack from the preloaded list of recent tasks. - */ - public TaskStack getTaskStack() { - return mStack; - } - - /** - * Returns the raw list of recent tasks. - */ - public List<ActivityManager.RecentTaskInfo> getRawTasks() { - return mRawTasks; - } - - /** Returns whether there are any tasks in any stacks. */ - public boolean hasTasks() { - if (mStack != null) { - return mStack.getTaskCount() > 0; - } - return false; - } - - /** - * Returns whether this task is too old to be shown. - */ - private boolean isHistoricalTask(ActivityManager.RecentTaskInfo t) { - return t.lastActiveTime < (System.currentTimeMillis() - SESSION_BEGIN_TIME); - } - - - /** - * Migrate the last active time from the prefs to the secure settings. - * - * The first time this runs, it will: - * 1) fetch the last stack active time from the prefs - * 2) set the prefs to the last stack active time for all users - * 3) clear the pref - * 4) return the last stack active time - * - * Subsequent calls to this will return zero. - */ - private long migrateLegacyLastStackActiveTime(int currentUserId) { - long legacyLastStackActiveTime = Prefs.getLong(mContext, - Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1); - if (legacyLastStackActiveTime != -1) { - Prefs.remove(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME); - UserManager userMgr = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - List<UserInfo> users = userMgr.getUsers(); - for (int i = 0; i < users.size(); i++) { - int userId = users.get(i).id; - if (userId != currentUserId) { - Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync( - legacyLastStackActiveTime, userId); - } - } - return legacyLastStackActiveTime; - } - return 0; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java deleted file mode 100644 index 1b893862cb7e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java +++ /dev/null @@ -1,663 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.model; - -import android.app.ActivityManager; -import android.content.ComponentCallbacks2; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Trace; -import android.util.Log; -import android.util.LruCache; - -import com.android.internal.annotations.GuardedBy; -import com.android.systemui.R; -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.RecentsConfiguration; -import com.android.systemui.recents.RecentsDebugFlags; -import com.android.systemui.recents.events.activity.PackagesChangedEvent; -import com.android.systemui.recents.misc.SystemServicesProxy; - -import java.io.PrintWriter; -import java.util.Map; -import java.util.concurrent.ConcurrentLinkedQueue; - - -/** - * A Task load queue - */ -class TaskResourceLoadQueue { - - ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>(); - - /** Adds a new task to the load queue */ - void addTask(Task t) { - if (!mQueue.contains(t)) { - mQueue.add(t); - } - synchronized(this) { - notifyAll(); - } - } - - /** - * Retrieves the next task from the load queue, as well as whether we want that task to be - * force reloaded. - */ - Task nextTask() { - return mQueue.poll(); - } - - /** Removes a task from the load queue */ - void removeTask(Task t) { - mQueue.remove(t); - } - - /** Clears all the tasks from the load queue */ - void clearTasks() { - mQueue.clear(); - } - - /** Returns whether the load queue is empty */ - boolean isEmpty() { - return mQueue.isEmpty(); - } -} - -/** - * Task resource loader - */ -class BackgroundTaskLoader implements Runnable { - static String TAG = "TaskResourceLoader"; - static boolean DEBUG = false; - - Context mContext; - HandlerThread mLoadThread; - Handler mLoadThreadHandler; - Handler mMainThreadHandler; - - TaskResourceLoadQueue mLoadQueue; - TaskKeyLruCache<Drawable> mIconCache; - BitmapDrawable mDefaultIcon; - - boolean mStarted; - boolean mCancelled; - boolean mWaitingOnLoadQueue; - - private final OnIdleChangedListener mOnIdleChangedListener; - - /** Constructor, creates a new loading thread that loads task resources in the background */ - public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue, - TaskKeyLruCache<Drawable> iconCache, BitmapDrawable defaultIcon, - OnIdleChangedListener onIdleChangedListener) { - mLoadQueue = loadQueue; - mIconCache = iconCache; - mDefaultIcon = defaultIcon; - mMainThreadHandler = new Handler(); - mOnIdleChangedListener = onIdleChangedListener; - mLoadThread = new HandlerThread("Recents-TaskResourceLoader", - android.os.Process.THREAD_PRIORITY_BACKGROUND); - mLoadThread.start(); - mLoadThreadHandler = new Handler(mLoadThread.getLooper()); - } - - /** Restarts the loader thread */ - void start(Context context) { - mContext = context; - mCancelled = false; - if (!mStarted) { - // Start loading on the load thread - mStarted = true; - mLoadThreadHandler.post(this); - } else { - // Notify the load thread to start loading again - synchronized (mLoadThread) { - mLoadThread.notifyAll(); - } - } - } - - /** Requests the loader thread to stop after the current iteration */ - void stop() { - // Mark as cancelled for the thread to pick up - mCancelled = true; - // If we are waiting for the load queue for more tasks, then we can just reset the - // Context now, since nothing is using it - if (mWaitingOnLoadQueue) { - mContext = null; - } - } - - @Override - public void run() { - while (true) { - if (mCancelled) { - // We have to unset the context here, since the background thread may be using it - // when we call stop() - mContext = null; - // If we are cancelled, then wait until we are started again - synchronized(mLoadThread) { - try { - mLoadThread.wait(); - } catch (InterruptedException ie) { - ie.printStackTrace(); - } - } - } else { - SystemServicesProxy ssp = Recents.getSystemServices(); - // If we've stopped the loader, then fall through to the above logic to wait on - // the load thread - if (ssp != null) { - processLoadQueueItem(ssp); - } - - // If there are no other items in the list, then just wait until something is added - if (!mCancelled && mLoadQueue.isEmpty()) { - synchronized(mLoadQueue) { - try { - mWaitingOnLoadQueue = true; - mMainThreadHandler.post( - () -> mOnIdleChangedListener.onIdleChanged(true)); - mLoadQueue.wait(); - mMainThreadHandler.post( - () -> mOnIdleChangedListener.onIdleChanged(false)); - mWaitingOnLoadQueue = false; - } catch (InterruptedException ie) { - ie.printStackTrace(); - } - } - } - } - } - } - - /** - * This needs to be in a separate method to work around an surprising interpreter behavior: - * The register will keep the local reference to cachedThumbnailData even if it falls out of - * scope. Putting it into a method fixes this issue. - */ - private void processLoadQueueItem(SystemServicesProxy ssp) { - // Load the next item from the queue - final Task t = mLoadQueue.nextTask(); - if (t != null) { - Drawable cachedIcon = mIconCache.get(t.key); - - // Load the icon if it is stale or we haven't cached one yet - if (cachedIcon == null) { - cachedIcon = ssp.getBadgedTaskDescriptionIcon(t.taskDescription, - t.key.userId, mContext.getResources()); - - if (cachedIcon == null) { - ActivityInfo info = ssp.getActivityInfo( - t.key.getComponent(), t.key.userId); - if (info != null) { - if (DEBUG) Log.d(TAG, "Loading icon: " + t.key); - cachedIcon = ssp.getBadgedActivityIcon(info, t.key.userId); - } - } - - if (cachedIcon == null) { - cachedIcon = mDefaultIcon; - } - - // At this point, even if we can't load the icon, we will set the - // default icon. - mIconCache.put(t.key, cachedIcon); - } - - if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key); - final ThumbnailData thumbnailData = ssp.getTaskThumbnail(t.key.id, - true /* reducedResolution */); - - if (!mCancelled) { - // Notify that the task data has changed - final Drawable finalIcon = cachedIcon; - mMainThreadHandler.post( - () -> t.notifyTaskDataLoaded(thumbnailData, finalIcon)); - } - } - } - - interface OnIdleChangedListener { - void onIdleChanged(boolean idle); - } -} - -/** - * Recents task loader - */ -public class RecentsTaskLoader { - - private static final String TAG = "RecentsTaskLoader"; - private static final boolean DEBUG = false; - - // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos - // for many tasks, which we use to get the activity labels and icons. Unlike the other caches - // below, this is per-package so we can't invalidate the items in the cache based on the last - // active time. Instead, we rely on the RecentsPackageMonitor to keep us informed whenever a - // package in the cache has been updated, so that we may remove it. - private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache; - private final TaskKeyLruCache<Drawable> mIconCache; - private final TaskKeyLruCache<String> mActivityLabelCache; - private final TaskKeyLruCache<String> mContentDescriptionCache; - private final TaskResourceLoadQueue mLoadQueue; - private final BackgroundTaskLoader mLoader; - private final HighResThumbnailLoader mHighResThumbnailLoader; - @GuardedBy("this") - private final TaskKeyStrongCache<ThumbnailData> mThumbnailCache = new TaskKeyStrongCache<>(); - @GuardedBy("this") - private final TaskKeyStrongCache<ThumbnailData> mTempCache = new TaskKeyStrongCache<>(); - private final int mMaxThumbnailCacheSize; - private final int mMaxIconCacheSize; - private int mNumVisibleTasksLoaded; - - int mDefaultTaskBarBackgroundColor; - int mDefaultTaskViewBackgroundColor; - BitmapDrawable mDefaultIcon; - - private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction = - new TaskKeyLruCache.EvictionCallback() { - @Override - public void onEntryEvicted(Task.TaskKey key) { - if (key != null) { - mActivityInfoCache.remove(key.getComponent()); - } - } - }; - - public RecentsTaskLoader(Context context) { - Resources res = context.getResources(); - mDefaultTaskBarBackgroundColor = - context.getColor(R.color.recents_task_bar_default_background_color); - mDefaultTaskViewBackgroundColor = - context.getColor(R.color.recents_task_view_default_background_color); - mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count); - mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count); - int iconCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 : - mMaxIconCacheSize; - - // Create the default assets - Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); - icon.eraseColor(0); - mDefaultIcon = new BitmapDrawable(context.getResources(), icon); - - // Initialize the proxy, cache and loaders - int numRecentTasks = ActivityManager.getMaxRecentTasksStatic(); - mHighResThumbnailLoader = new HighResThumbnailLoader(Recents.getSystemServices(), - Looper.getMainLooper(), Recents.getConfiguration().isLowRamDevice); - mLoadQueue = new TaskResourceLoadQueue(); - mIconCache = new TaskKeyLruCache<>(iconCacheSize, mClearActivityInfoOnEviction); - mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction); - mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks, - mClearActivityInfoOnEviction); - mActivityInfoCache = new LruCache(numRecentTasks); - mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultIcon, - mHighResThumbnailLoader::setTaskLoadQueueIdle); - } - - /** Returns the size of the app icon cache. */ - public int getIconCacheSize() { - return mMaxIconCacheSize; - } - - /** Returns the size of the thumbnail cache. */ - public int getThumbnailCacheSize() { - return mMaxThumbnailCacheSize; - } - - public HighResThumbnailLoader getHighResThumbnailLoader() { - return mHighResThumbnailLoader; - } - - /** Creates a new plan for loading the recent tasks. */ - public RecentsTaskLoadPlan createLoadPlan(Context context) { - RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context); - return plan; - } - - /** Preloads raw recents tasks using the specified plan to store the output. */ - public synchronized void preloadRawTasks(RecentsTaskLoadPlan plan, - boolean includeFrontMostExcludedTask) { - plan.preloadRawTasks(includeFrontMostExcludedTask); - } - - /** Preloads recents tasks using the specified plan to store the output. */ - public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId, - boolean includeFrontMostExcludedTask) { - try { - Trace.beginSection("preloadPlan"); - plan.preloadPlan(this, runningTaskId, includeFrontMostExcludedTask); - } finally { - Trace.endSection(); - } - } - - /** Begins loading the heavy task data according to the specified options. */ - public synchronized void loadTasks(Context context, RecentsTaskLoadPlan plan, - RecentsTaskLoadPlan.Options opts) { - if (opts == null) { - throw new RuntimeException("Requires load options"); - } - if (opts.onlyLoadForCache && opts.loadThumbnails) { - - // If we are loading for the cache, we'd like to have the real cache only include the - // visible thumbnails. However, we also don't want to reload already cached thumbnails. - // Thus, we copy over the current entries into a second cache, and clear the real cache, - // such that the real cache only contains visible thumbnails. - mTempCache.copyEntries(mThumbnailCache); - mThumbnailCache.evictAll(); - } - plan.executePlan(opts, this); - mTempCache.evictAll(); - if (!opts.onlyLoadForCache) { - mNumVisibleTasksLoaded = opts.numVisibleTasks; - } - } - - /** - * Acquires the task resource data directly from the cache, loading if necessary. - */ - public void loadTaskData(Task t) { - Drawable icon = mIconCache.getAndInvalidateIfModified(t.key); - icon = icon != null ? icon : mDefaultIcon; - mLoadQueue.addTask(t); - t.notifyTaskDataLoaded(t.thumbnail, icon); - } - - /** Releases the task resource data back into the pool. */ - public void unloadTaskData(Task t) { - mLoadQueue.removeTask(t); - t.notifyTaskDataUnloaded(mDefaultIcon); - } - - /** Completely removes the resource data from the pool. */ - public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) { - mLoadQueue.removeTask(t); - mIconCache.remove(t.key); - mActivityLabelCache.remove(t.key); - mContentDescriptionCache.remove(t.key); - if (notifyTaskDataUnloaded) { - t.notifyTaskDataUnloaded(mDefaultIcon); - } - } - - /** - * Handles signals from the system, trimming memory when requested to prevent us from running - * out of memory. - */ - public synchronized void onTrimMemory(int level) { - switch (level) { - case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN: - // Stop the loader immediately when the UI is no longer visible - stopLoader(); - mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded, - mMaxIconCacheSize / 2)); - break; - case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: - case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: - // We are leaving recents, so trim the data a bit - mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2)); - mActivityInfoCache.trimToSize(Math.max(1, - ActivityManager.getMaxRecentTasksStatic() / 2)); - break; - case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: - case ComponentCallbacks2.TRIM_MEMORY_MODERATE: - // We are going to be low on memory - mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4)); - mActivityInfoCache.trimToSize(Math.max(1, - ActivityManager.getMaxRecentTasksStatic() / 4)); - break; - case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: - case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: - // We are low on memory, so release everything - mIconCache.evictAll(); - mActivityInfoCache.evictAll(); - // The cache is small, only clear the label cache when we are critical - mActivityLabelCache.evictAll(); - mContentDescriptionCache.evictAll(); - mThumbnailCache.evictAll(); - break; - default: - break; - } - } - - /** - * Returns the cached task label if the task key is not expired, updating the cache if it is. - */ - String getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td) { - SystemServicesProxy ssp = Recents.getSystemServices(); - - // Return the task description label if it exists - if (td != null && td.getLabel() != null) { - return td.getLabel(); - } - // Return the cached activity label if it exists - String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey); - if (label != null) { - return label; - } - // All short paths failed, load the label from the activity info and cache it - ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); - if (activityInfo != null) { - label = ssp.getBadgedActivityLabel(activityInfo, taskKey.userId); - mActivityLabelCache.put(taskKey, label); - return label; - } - // If the activity info does not exist or fails to load, return an empty label for now, - // but do not cache it - return ""; - } - - /** - * Returns the cached task content description if the task key is not expired, updating the - * cache if it is. - */ - String getAndUpdateContentDescription(Task.TaskKey taskKey, ActivityManager.TaskDescription td, - Resources res) { - SystemServicesProxy ssp = Recents.getSystemServices(); - - // Return the cached content description if it exists - String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey); - if (label != null) { - return label; - } - - // All short paths failed, load the label from the activity info and cache it - ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); - if (activityInfo != null) { - label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, td, res); - if (td == null) { - // Only add to the cache if the task description is null, otherwise, it is possible - // for the task description to change between calls without the last active time - // changing (ie. between preloading and Overview starting) which would lead to stale - // content descriptions - // TODO: Investigate improving this - mContentDescriptionCache.put(taskKey, label); - } - return label; - } - // If the content description does not exist, return an empty label for now, but do not - // cache it - return ""; - } - - /** - * Returns the cached task icon if the task key is not expired, updating the cache if it is. - */ - Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td, - Resources res, boolean loadIfNotCached) { - SystemServicesProxy ssp = Recents.getSystemServices(); - - // Return the cached activity icon if it exists - Drawable icon = mIconCache.getAndInvalidateIfModified(taskKey); - if (icon != null) { - return icon; - } - - if (loadIfNotCached) { - // Return and cache the task description icon if it exists - icon = ssp.getBadgedTaskDescriptionIcon(td, taskKey.userId, res); - if (icon != null) { - mIconCache.put(taskKey, icon); - return icon; - } - - // Load the icon from the activity info and cache it - ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); - if (activityInfo != null) { - icon = ssp.getBadgedActivityIcon(activityInfo, taskKey.userId); - if (icon != null) { - mIconCache.put(taskKey, icon); - return icon; - } - } - } - // We couldn't load any icon - return null; - } - - /** - * Returns the cached thumbnail if the task key is not expired, updating the cache if it is. - */ - synchronized ThumbnailData getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached, - boolean storeInCache) { - SystemServicesProxy ssp = Recents.getSystemServices(); - - ThumbnailData cached = mThumbnailCache.getAndInvalidateIfModified(taskKey); - if (cached != null) { - return cached; - } - - cached = mTempCache.getAndInvalidateIfModified(taskKey); - if (cached != null) { - mThumbnailCache.put(taskKey, cached); - return cached; - } - - if (loadIfNotCached) { - RecentsConfiguration config = Recents.getConfiguration(); - if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) { - // Load the thumbnail from the system - ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id, - true /* reducedResolution */); - if (thumbnailData.thumbnail != null) { - if (storeInCache) { - mThumbnailCache.put(taskKey, thumbnailData); - } - return thumbnailData; - } - } - } - - // We couldn't load any thumbnail - return null; - } - - /** - * Returns the task's primary color if possible, defaulting to the default color if there is - * no specified primary color. - */ - int getActivityPrimaryColor(ActivityManager.TaskDescription td) { - if (td != null && td.getPrimaryColor() != 0) { - return td.getPrimaryColor(); - } - return mDefaultTaskBarBackgroundColor; - } - - /** - * Returns the task's background color if possible. - */ - int getActivityBackgroundColor(ActivityManager.TaskDescription td) { - if (td != null && td.getBackgroundColor() != 0) { - return td.getBackgroundColor(); - } - return mDefaultTaskViewBackgroundColor; - } - - /** - * Returns the activity info for the given task key, retrieving one from the system if the - * task key is expired. - */ - ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) { - SystemServicesProxy ssp = Recents.getSystemServices(); - ComponentName cn = taskKey.getComponent(); - ActivityInfo activityInfo = mActivityInfoCache.get(cn); - if (activityInfo == null) { - activityInfo = ssp.getActivityInfo(cn, taskKey.userId); - if (cn == null || activityInfo == null) { - Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " + - activityInfo); - return null; - } - mActivityInfoCache.put(cn, activityInfo); - } - return activityInfo; - } - - /** - * Starts loading tasks. - */ - public void startLoader(Context ctx) { - mLoader.start(ctx); - } - - /** - * Stops the task loader and clears all queued, pending task loads. - */ - private void stopLoader() { - mLoader.stop(); - mLoadQueue.clearTasks(); - } - - /**** Event Bus Events ****/ - - public final void onBusEvent(PackagesChangedEvent event) { - // Remove all the cached activity infos for this package. The other caches do not need to - // be pruned at this time, as the TaskKey expiration checks will flush them next time their - // cached contents are requested - Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot(); - for (ComponentName cn : activityInfoCache.keySet()) { - if (cn.getPackageName().equals(event.packageName)) { - if (DEBUG) { - Log.d(TAG, "Removing activity info from cache: " + cn); - } - mActivityInfoCache.remove(cn); - } - } - } - - public synchronized void dump(String prefix, PrintWriter writer) { - String innerPrefix = prefix + " "; - - writer.print(prefix); writer.println(TAG); - writer.print(prefix); writer.println("Icon Cache"); - mIconCache.dump(innerPrefix, writer); - writer.print(prefix); writer.println("Thumbnail Cache"); - mThumbnailCache.dump(innerPrefix, writer); - writer.print(prefix); writer.println("Temp Thumbnail Cache"); - mTempCache.dump(innerPrefix, writer); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java deleted file mode 100644 index 29d0a2372769..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.model; - -import android.app.ActivityManager; -import android.app.ActivityManager.TaskThumbnail; -import android.content.ComponentName; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.view.ViewDebug; - -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.Utilities; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Objects; - - -/** - * A task represents the top most task in the system's task stack. - */ -public class Task { - - public static final String TAG = "Task"; - - /* Task callbacks */ - public interface TaskCallbacks { - /* Notifies when a task has been bound */ - public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData); - /* Notifies when a task has been unbound */ - public void onTaskDataUnloaded(); - /* Notifies when a task's stack id has changed. */ - public void onTaskStackIdChanged(); - } - - /* The Task Key represents the unique primary key for the task */ - public static class TaskKey { - @ViewDebug.ExportedProperty(category="recents") - public final int id; - @ViewDebug.ExportedProperty(category="recents") - public int stackId; - @ViewDebug.ExportedProperty(category="recents") - public final Intent baseIntent; - @ViewDebug.ExportedProperty(category="recents") - public final int userId; - @ViewDebug.ExportedProperty(category="recents") - public long firstActiveTime; - @ViewDebug.ExportedProperty(category="recents") - public long lastActiveTime; - - private int mHashCode; - - public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime, - long lastActiveTime) { - this.id = id; - this.stackId = stackId; - this.baseIntent = intent; - this.userId = userId; - this.firstActiveTime = firstActiveTime; - this.lastActiveTime = lastActiveTime; - updateHashCode(); - } - - public void setStackId(int stackId) { - this.stackId = stackId; - updateHashCode(); - } - - public ComponentName getComponent() { - return this.baseIntent.getComponent(); - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof TaskKey)) { - return false; - } - TaskKey otherKey = (TaskKey) o; - return id == otherKey.id && stackId == otherKey.stackId && userId == otherKey.userId; - } - - @Override - public int hashCode() { - return mHashCode; - } - - @Override - public String toString() { - return "id=" + id + " stackId=" + stackId + " user=" + userId + " lastActiveTime=" + - lastActiveTime; - } - - private void updateHashCode() { - mHashCode = Objects.hash(id, stackId, userId); - } - } - - @ViewDebug.ExportedProperty(deepExport=true, prefix="key_") - public TaskKey key; - - /** - * The temporary sort index in the stack, used when ordering the stack. - */ - public int temporarySortIndexInStack; - - /** - * The group will be computed separately from the initialization of the task - */ - @ViewDebug.ExportedProperty(deepExport=true, prefix="group_") - public TaskGrouping group; - /** - * The affiliationTaskId is the task id of the parent task or itself if it is not affiliated - * with any task. - */ - @ViewDebug.ExportedProperty(category="recents") - public int affiliationTaskId; - @ViewDebug.ExportedProperty(category="recents") - public int affiliationColor; - - /** - * The icon is the task description icon (if provided), which falls back to the activity icon, - * which can then fall back to the application icon. - */ - public Drawable icon; - public ThumbnailData thumbnail; - @ViewDebug.ExportedProperty(category="recents") - public String title; - @ViewDebug.ExportedProperty(category="recents") - public String titleDescription; - @ViewDebug.ExportedProperty(category="recents") - public String dismissDescription; - @ViewDebug.ExportedProperty(category="recents") - public String appInfoDescription; - @ViewDebug.ExportedProperty(category="recents") - public int colorPrimary; - @ViewDebug.ExportedProperty(category="recents") - public int colorBackground; - @ViewDebug.ExportedProperty(category="recents") - public boolean useLightOnPrimaryColor; - - /** - * The bounds of the task, used only if it is a freeform task. - */ - @ViewDebug.ExportedProperty(category="recents") - public Rect bounds; - - /** - * The task description for this task, only used to reload task icons. - */ - public ActivityManager.TaskDescription taskDescription; - - /** - * The state isLaunchTarget will be set for the correct task upon launching Recents. - */ - @ViewDebug.ExportedProperty(category="recents") - public boolean isLaunchTarget; - @ViewDebug.ExportedProperty(category="recents") - public boolean isStackTask; - @ViewDebug.ExportedProperty(category="recents") - public boolean isSystemApp; - @ViewDebug.ExportedProperty(category="recents") - public boolean isDockable; - - /** - * Resize mode. See {@link ActivityInfo#resizeMode}. - */ - @ViewDebug.ExportedProperty(category="recents") - public int resizeMode; - - @ViewDebug.ExportedProperty(category="recents") - public ComponentName topActivity; - - @ViewDebug.ExportedProperty(category="recents") - public boolean isLocked; - - private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>(); - - public Task() { - // Do nothing - } - - public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon, - ThumbnailData thumbnail, String title, String titleDescription, - String dismissDescription, String appInfoDescription, int colorPrimary, - int colorBackground, boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp, - boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription, - int resizeMode, ComponentName topActivity, boolean isLocked) { - boolean isInAffiliationGroup = (affiliationTaskId != key.id); - boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0); - this.key = key; - this.affiliationTaskId = affiliationTaskId; - this.affiliationColor = affiliationColor; - this.icon = icon; - this.thumbnail = thumbnail; - this.title = title; - this.titleDescription = titleDescription; - this.dismissDescription = dismissDescription; - this.appInfoDescription = appInfoDescription; - this.colorPrimary = hasAffiliationGroupColor ? affiliationColor : colorPrimary; - this.colorBackground = colorBackground; - this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary, - Color.WHITE) > 3f; - this.bounds = bounds; - this.taskDescription = taskDescription; - this.isLaunchTarget = isLaunchTarget; - this.isStackTask = isStackTask; - this.isSystemApp = isSystemApp; - this.isDockable = isDockable; - this.resizeMode = resizeMode; - this.topActivity = topActivity; - this.isLocked = isLocked; - } - - /** - * Copies the metadata from another task, but retains the current callbacks. - */ - public void copyFrom(Task o) { - this.key = o.key; - this.group = o.group; - this.affiliationTaskId = o.affiliationTaskId; - this.affiliationColor = o.affiliationColor; - this.icon = o.icon; - this.thumbnail = o.thumbnail; - this.title = o.title; - this.titleDescription = o.titleDescription; - this.dismissDescription = o.dismissDescription; - this.appInfoDescription = o.appInfoDescription; - this.colorPrimary = o.colorPrimary; - this.colorBackground = o.colorBackground; - this.useLightOnPrimaryColor = o.useLightOnPrimaryColor; - this.bounds = o.bounds; - this.taskDescription = o.taskDescription; - this.isLaunchTarget = o.isLaunchTarget; - this.isStackTask = o.isStackTask; - this.isSystemApp = o.isSystemApp; - this.isDockable = o.isDockable; - this.resizeMode = o.resizeMode; - this.isLocked = o.isLocked; - this.topActivity = o.topActivity; - } - - /** - * Add a callback. - */ - public void addCallback(TaskCallbacks cb) { - if (!mCallbacks.contains(cb)) { - mCallbacks.add(cb); - } - } - - /** - * Remove a callback. - */ - public void removeCallback(TaskCallbacks cb) { - mCallbacks.remove(cb); - } - - /** Set the grouping */ - public void setGroup(TaskGrouping group) { - this.group = group; - } - - /** - * Updates the stack id of this task. - */ - public void setStackId(int stackId) { - key.setStackId(stackId); - int callbackCount = mCallbacks.size(); - for (int i = 0; i < callbackCount; i++) { - mCallbacks.get(i).onTaskStackIdChanged(); - } - } - - /** - * Returns whether this task is on the freeform task stack. - */ - public boolean isFreeformTask() { - SystemServicesProxy ssp = Recents.getSystemServices(); - return ssp.hasFreeformWorkspaceSupport() && ssp.isFreeformStack(key.stackId); - } - - /** Notifies the callback listeners that this task has been loaded */ - public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) { - this.icon = applicationIcon; - this.thumbnail = thumbnailData; - int callbackCount = mCallbacks.size(); - for (int i = 0; i < callbackCount; i++) { - mCallbacks.get(i).onTaskDataLoaded(this, thumbnailData); - } - } - - /** Notifies the callback listeners that this task has been unloaded */ - public void notifyTaskDataUnloaded(Drawable defaultApplicationIcon) { - icon = defaultApplicationIcon; - thumbnail = null; - for (int i = mCallbacks.size() - 1; i >= 0; i--) { - mCallbacks.get(i).onTaskDataUnloaded(); - } - } - - /** - * Returns whether this task is affiliated with another task. - */ - public boolean isAffiliatedTask() { - return key.id != affiliationTaskId; - } - - /** - * Returns the top activity component. - */ - public ComponentName getTopComponent() { - return topActivity != null - ? topActivity - : key.baseIntent.getComponent(); - } - - @Override - public boolean equals(Object o) { - // Check that the id matches - Task t = (Task) o; - return key.equals(t.key); - } - - @Override - public String toString() { - return "[" + key.toString() + "] " + title; - } - - public void dump(String prefix, PrintWriter writer) { - writer.print(prefix); writer.print(key); - if (isAffiliatedTask()) { - writer.print(" "); writer.print("affTaskId=" + affiliationTaskId); - } - if (!isDockable) { - writer.print(" dockable=N"); - } - if (isLaunchTarget) { - writer.print(" launchTarget=Y"); - } - if (isFreeformTask()) { - writer.print(" freeform=Y"); - } - if (isLocked) { - writer.print(" locked=Y"); - } - writer.print(" "); writer.print(title); - writer.println(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java deleted file mode 100644 index 2109376d4ff3..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.android.systemui.recents.model; - -import android.util.ArrayMap; - -import java.util.ArrayList; - -/** Represents a grouping of tasks witihin a stack. */ -public class TaskGrouping { - - int affiliation; - long latestActiveTimeInGroup; - - Task.TaskKey mFrontMostTaskKey; - ArrayList<Task.TaskKey> mTaskKeys = new ArrayList<Task.TaskKey>(); - ArrayMap<Task.TaskKey, Integer> mTaskKeyIndices = new ArrayMap<>(); - - /** Creates a group with a specified affiliation. */ - public TaskGrouping(int affiliation) { - this.affiliation = affiliation; - } - - /** Adds a new task to this group. */ - void addTask(Task t) { - mTaskKeys.add(t.key); - if (t.key.lastActiveTime > latestActiveTimeInGroup) { - latestActiveTimeInGroup = t.key.lastActiveTime; - } - t.setGroup(this); - updateTaskIndices(); - } - - /** Removes a task from this group. */ - void removeTask(Task t) { - mTaskKeys.remove(t.key); - latestActiveTimeInGroup = 0; - int taskCount = mTaskKeys.size(); - for (int i = 0; i < taskCount; i++) { - long lastActiveTime = mTaskKeys.get(i).lastActiveTime; - if (lastActiveTime > latestActiveTimeInGroup) { - latestActiveTimeInGroup = lastActiveTime; - } - } - t.setGroup(null); - updateTaskIndices(); - } - - /** Returns the key of the next task in the group. */ - public Task.TaskKey getNextTaskInGroup(Task t) { - int i = indexOf(t); - if ((i + 1) < getTaskCount()) { - return mTaskKeys.get(i + 1); - } - return null; - } - - /** Returns the key of the previous task in the group. */ - public Task.TaskKey getPrevTaskInGroup(Task t) { - int i = indexOf(t); - if ((i - 1) >= 0) { - return mTaskKeys.get(i - 1); - } - return null; - } - - /** Gets the front task */ - public boolean isFrontMostTask(Task t) { - return (t.key == mFrontMostTaskKey); - } - - /** Finds the index of a given task in a group. */ - public int indexOf(Task t) { - return mTaskKeyIndices.get(t.key); - } - - /** Returns whether a task is in this grouping. */ - public boolean containsTask(Task t) { - return mTaskKeyIndices.containsKey(t.key); - } - - /** Returns whether one task is above another in the group. If they are not in the same group, - * this returns false. */ - public boolean isTaskAboveTask(Task t, Task below) { - return mTaskKeyIndices.containsKey(t.key) && mTaskKeyIndices.containsKey(below.key) && - mTaskKeyIndices.get(t.key) > mTaskKeyIndices.get(below.key); - } - - /** Returns the number of tasks in this group. */ - public int getTaskCount() { return mTaskKeys.size(); } - - /** Updates the mapping of tasks to indices. */ - private void updateTaskIndices() { - if (mTaskKeys.isEmpty()) { - mFrontMostTaskKey = null; - mTaskKeyIndices.clear(); - return; - } - - int taskCount = mTaskKeys.size(); - mFrontMostTaskKey = mTaskKeys.get(mTaskKeys.size() - 1); - mTaskKeyIndices.clear(); - for (int i = 0; i < taskCount; i++) { - Task.TaskKey k = mTaskKeys.get(i); - mTaskKeyIndices.put(k, i); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyCache.java deleted file mode 100644 index be99f93028dc..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyCache.java +++ /dev/null @@ -1,89 +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 com.android.systemui.recents.model; - -import android.util.Log; -import android.util.SparseArray; - -import com.android.systemui.recents.model.Task.TaskKey; - -/** - * Base class for both strong and LRU task key cache. - */ -public abstract class TaskKeyCache<V> { - - protected static final String TAG = "TaskKeyCache"; - - protected final SparseArray<TaskKey> mKeys = new SparseArray<>(); - - /** - * Gets a specific entry in the cache with the specified key, regardless of whether the cached - * value is valid or not. - */ - final V get(Task.TaskKey key) { - return getCacheEntry(key.id); - } - - /** - * Returns the value only if the key is valid (has not been updated since the last time it was - * in the cache) - */ - final V getAndInvalidateIfModified(Task.TaskKey key) { - Task.TaskKey lastKey = mKeys.get(key.id); - if (lastKey != null) { - if ((lastKey.stackId != key.stackId) || - (lastKey.lastActiveTime != key.lastActiveTime)) { - // The task has updated (been made active since the last time it was put into the - // LRU cache) or the stack id for the task has changed, invalidate that cache item - remove(key); - return null; - } - } - // Either the task does not exist in the cache, or the last active time is the same as - // the key specified, so return what is in the cache - return getCacheEntry(key.id); - } - - /** Puts an entry in the cache for a specific key. */ - final void put(Task.TaskKey key, V value) { - if (key == null || value == null) { - Log.e(TAG, "Unexpected null key or value: " + key + ", " + value); - return; - } - mKeys.put(key.id, key); - putCacheEntry(key.id, value); - } - - - /** Removes a cache entry for a specific key. */ - final void remove(Task.TaskKey key) { - // Remove the key after the cache value because we need it to make the callback - removeCacheEntry(key.id); - mKeys.remove(key.id); - } - - /** Removes all the entries in the cache. */ - final void evictAll() { - evictAllCache(); - mKeys.clear(); - } - - protected abstract V getCacheEntry(int id); - protected abstract void putCacheEntry(int id, V value); - protected abstract void removeCacheEntry(int id); - protected abstract void evictAllCache(); -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java deleted file mode 100644 index 778df6be399b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.model; - -import android.util.LruCache; - -import java.io.PrintWriter; - -/** - * A mapping of {@link Task.TaskKey} to value, with additional LRU functionality where the least - * recently referenced key/values will be evicted as more values than the given cache size are - * inserted. - * - * In addition, this also allows the caller to invalidate cached values for keys that have since - * changed. - */ -public class TaskKeyLruCache<V> extends TaskKeyCache<V> { - - public interface EvictionCallback { - public void onEntryEvicted(Task.TaskKey key); - } - - private final LruCache<Integer, V> mCache; - private final EvictionCallback mEvictionCallback; - - public TaskKeyLruCache(int cacheSize) { - this(cacheSize, null); - } - - public TaskKeyLruCache(int cacheSize, EvictionCallback evictionCallback) { - mEvictionCallback = evictionCallback; - mCache = new LruCache<Integer, V>(cacheSize) { - - @Override - protected void entryRemoved(boolean evicted, Integer taskId, V oldV, V newV) { - if (mEvictionCallback != null) { - mEvictionCallback.onEntryEvicted(mKeys.get(taskId)); - } - mKeys.remove(taskId); - } - }; - } - - /** Trims the cache to a specific size */ - final void trimToSize(int cacheSize) { - mCache.trimToSize(cacheSize); - } - - public void dump(String prefix, PrintWriter writer) { - String innerPrefix = prefix + " "; - - writer.print(prefix); writer.print(TAG); - writer.print(" numEntries="); writer.print(mKeys.size()); - writer.println(); - int keyCount = mKeys.size(); - for (int i = 0; i < keyCount; i++) { - writer.print(innerPrefix); writer.println(mKeys.get(mKeys.keyAt(i))); - } - } - - @Override - protected V getCacheEntry(int id) { - return mCache.get(id); - } - - @Override - protected void putCacheEntry(int id, V value) { - mCache.put(id, value); - } - - @Override - protected void removeCacheEntry(int id) { - mCache.remove(id); - } - - @Override - protected void evictAllCache() { - mCache.evictAll(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyStrongCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyStrongCache.java deleted file mode 100644 index c84df8a14288..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyStrongCache.java +++ /dev/null @@ -1,73 +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 com.android.systemui.recents.model; - -import android.util.ArrayMap; -import android.util.Log; -import android.util.SparseArray; - -import com.android.systemui.recents.model.Task.TaskKey; - -import java.io.PrintWriter; - -/** - * Like {@link TaskKeyLruCache}, but without LRU functionality. - */ -public class TaskKeyStrongCache<V> extends TaskKeyCache<V> { - - private static final String TAG = "TaskKeyCache"; - - private final ArrayMap<Integer, V> mCache = new ArrayMap<>(); - - final void copyEntries(TaskKeyStrongCache<V> other) { - for (int i = other.mKeys.size() - 1; i >= 0; i--) { - TaskKey key = other.mKeys.valueAt(i); - put(key, other.mCache.get(key.id)); - } - } - - public void dump(String prefix, PrintWriter writer) { - String innerPrefix = prefix + " "; - writer.print(prefix); writer.print(TAG); - writer.print(" numEntries="); writer.print(mKeys.size()); - writer.println(); - int keyCount = mKeys.size(); - for (int i = 0; i < keyCount; i++) { - writer.print(innerPrefix); writer.println(mKeys.get(mKeys.keyAt(i))); - } - } - - @Override - protected V getCacheEntry(int id) { - return mCache.get(id); - } - - @Override - protected void putCacheEntry(int id, V value) { - mCache.put(id, value); - } - - @Override - protected void removeCacheEntry(int id) { - mCache.remove(id); - } - - @Override - protected void evictAllCache() { - mCache.clear(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java deleted file mode 100644 index 6e3be09b2ae9..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ /dev/null @@ -1,1142 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.model; - -import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; -import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; -import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; -import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; -import static android.view.WindowManager.DOCKED_BOTTOM; -import static android.view.WindowManager.DOCKED_INVALID; -import static android.view.WindowManager.DOCKED_LEFT; -import static android.view.WindowManager.DOCKED_RIGHT; -import static android.view.WindowManager.DOCKED_TOP; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.annotation.IntDef; -import android.content.ComponentName; -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.ColorDrawable; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.IntProperty; -import android.util.SparseArray; -import android.view.animation.Interpolator; - -import com.android.internal.policy.DockedDividerUtils; -import com.android.systemui.Interpolators; -import com.android.systemui.R; -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.RecentsDebugFlags; -import com.android.systemui.recents.misc.NamedCounter; -import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.Utilities; -import com.android.systemui.recents.views.AnimationProps; -import com.android.systemui.recents.views.DropTarget; -import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; - -import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Random; - - -/** - * An interface for a task filter to query whether a particular task should show in a stack. - */ -interface TaskFilter { - /** Returns whether the filter accepts the specified task */ - public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index); -} - -/** - * A list of filtered tasks. - */ -class FilteredTaskList { - - ArrayList<Task> mTasks = new ArrayList<>(); - ArrayList<Task> mFilteredTasks = new ArrayList<>(); - ArrayMap<Task.TaskKey, Integer> mTaskIndices = new ArrayMap<>(); - TaskFilter mFilter; - - /** Sets the task filter, saving the current touch state */ - boolean setFilter(TaskFilter filter) { - ArrayList<Task> prevFilteredTasks = new ArrayList<>(mFilteredTasks); - mFilter = filter; - updateFilteredTasks(); - if (!prevFilteredTasks.equals(mFilteredTasks)) { - return true; - } else { - return false; - } - } - - /** Removes the task filter and returns the previous touch state */ - void removeFilter() { - mFilter = null; - updateFilteredTasks(); - } - - /** Adds a new task to the task list */ - void add(Task t) { - mTasks.add(t); - updateFilteredTasks(); - } - - /** - * Moves the given task. - */ - public void moveTaskToStack(Task task, int insertIndex, int newStackId) { - int taskIndex = indexOf(task); - if (taskIndex != insertIndex) { - mTasks.remove(taskIndex); - if (taskIndex < insertIndex) { - insertIndex--; - } - mTasks.add(insertIndex, task); - } - - // Update the stack id now, after we've moved the task, and before we update the - // filtered tasks - task.setStackId(newStackId); - updateFilteredTasks(); - } - - /** Sets the list of tasks */ - void set(List<Task> tasks) { - mTasks.clear(); - mTasks.addAll(tasks); - updateFilteredTasks(); - } - - /** Removes a task from the base list only if it is in the filtered list */ - boolean remove(Task t) { - if (mFilteredTasks.contains(t)) { - boolean removed = mTasks.remove(t); - updateFilteredTasks(); - return removed; - } - return false; - } - - /** Returns the index of this task in the list of filtered tasks */ - int indexOf(Task t) { - if (t != null && mTaskIndices.containsKey(t.key)) { - return mTaskIndices.get(t.key); - } - return -1; - } - - /** Returns the size of the list of filtered tasks */ - int size() { - return mFilteredTasks.size(); - } - - /** Returns whether the filtered list contains this task */ - boolean contains(Task t) { - return mTaskIndices.containsKey(t.key); - } - - /** Updates the list of filtered tasks whenever the base task list changes */ - private void updateFilteredTasks() { - mFilteredTasks.clear(); - if (mFilter != null) { - // Create a sparse array from task id to Task - SparseArray<Task> taskIdMap = new SparseArray<>(); - int taskCount = mTasks.size(); - for (int i = 0; i < taskCount; i++) { - Task t = mTasks.get(i); - taskIdMap.put(t.key.id, t); - } - - for (int i = 0; i < taskCount; i++) { - Task t = mTasks.get(i); - if (mFilter.acceptTask(taskIdMap, t, i)) { - mFilteredTasks.add(t); - } - } - } else { - mFilteredTasks.addAll(mTasks); - } - updateFilteredTaskIndices(); - } - - /** Updates the mapping of tasks to indices. */ - private void updateFilteredTaskIndices() { - int taskCount = mFilteredTasks.size(); - mTaskIndices.clear(); - for (int i = 0; i < taskCount; i++) { - Task t = mFilteredTasks.get(i); - mTaskIndices.put(t.key, i); - } - } - - /** Returns whether this task list is filtered */ - boolean hasFilter() { - return (mFilter != null); - } - - /** Returns the list of filtered tasks */ - ArrayList<Task> getTasks() { - return mFilteredTasks; - } -} - -/** - * The task stack contains a list of multiple tasks. - */ -public class TaskStack { - - private static final String TAG = "TaskStack"; - - /** Task stack callbacks */ - public interface TaskStackCallbacks { - /** - * Notifies when a new task has been added to the stack. - */ - void onStackTaskAdded(TaskStack stack, Task newTask); - - /** - * Notifies when a task has been removed from the stack. - */ - void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask, - AnimationProps animation, boolean fromDockGesture, - boolean dismissRecentsIfAllRemoved); - - /** - * Notifies when all tasks have been removed from the stack. - */ - void onStackTasksRemoved(TaskStack stack); - - /** - * Notifies when tasks in the stack have been updated. - */ - void onStackTasksUpdated(TaskStack stack); - } - - /** - * The various possible dock states when dragging and dropping a task. - */ - public static class DockState implements DropTarget { - - public static final int DOCK_AREA_BG_COLOR = 0xFFffffff; - public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000; - - // The rotation to apply to the hint text - @Retention(RetentionPolicy.SOURCE) - @IntDef({HORIZONTAL, VERTICAL}) - public @interface TextOrientation {} - private static final int HORIZONTAL = 0; - private static final int VERTICAL = 1; - - private static final int DOCK_AREA_ALPHA = 80; - public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL, - null, null, null); - public static final DockState LEFT = new DockState(DOCKED_LEFT, - DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL, - new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1), - new RectF(0, 0, 0.5f, 1)); - public static final DockState TOP = new DockState(DOCKED_TOP, - DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL, - new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f), - new RectF(0, 0, 1, 0.5f)); - public static final DockState RIGHT = new DockState(DOCKED_RIGHT, - DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL, - new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1), - new RectF(0.5f, 0, 1, 1)); - public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM, - DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL, - new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1), - new RectF(0, 0.5f, 1, 1)); - - @Override - public boolean acceptsDrop(int x, int y, int width, int height, Rect insets, - boolean isCurrentTarget) { - if (isCurrentTarget) { - getMappedRect(expandedTouchDockArea, width, height, mTmpRect); - return mTmpRect.contains(x, y); - } else { - getMappedRect(touchArea, width, height, mTmpRect); - updateBoundsWithSystemInsets(mTmpRect, insets); - return mTmpRect.contains(x, y); - } - } - - // Represents the view state of this dock state - public static class ViewState { - private static final IntProperty<ViewState> HINT_ALPHA = - new IntProperty<ViewState>("drawableAlpha") { - @Override - public void setValue(ViewState object, int alpha) { - object.mHintTextAlpha = alpha; - object.dockAreaOverlay.invalidateSelf(); - } - - @Override - public Integer get(ViewState object) { - return object.mHintTextAlpha; - } - }; - - public final int dockAreaAlpha; - public final ColorDrawable dockAreaOverlay; - public final int hintTextAlpha; - public final int hintTextOrientation; - - private final int mHintTextResId; - private String mHintText; - private Paint mHintTextPaint; - private Point mHintTextBounds = new Point(); - private int mHintTextAlpha = 255; - private AnimatorSet mDockAreaOverlayAnimator; - private Rect mTmpRect = new Rect(); - - private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation, - int hintTextResId) { - dockAreaAlpha = areaAlpha; - dockAreaOverlay = new ColorDrawable(Recents.getConfiguration().isGridEnabled - ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR); - dockAreaOverlay.setAlpha(0); - hintTextAlpha = hintAlpha; - hintTextOrientation = hintOrientation; - mHintTextResId = hintTextResId; - mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mHintTextPaint.setColor(Color.WHITE); - } - - /** - * Updates the view state with the given context. - */ - public void update(Context context) { - Resources res = context.getResources(); - mHintText = context.getString(mHintTextResId); - mHintTextPaint.setTextSize(res.getDimensionPixelSize( - R.dimen.recents_drag_hint_text_size)); - mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mTmpRect); - mHintTextBounds.set((int) mHintTextPaint.measureText(mHintText), mTmpRect.height()); - } - - /** - * Draws the current view state. - */ - public void draw(Canvas canvas) { - // Draw the overlay background - if (dockAreaOverlay.getAlpha() > 0) { - dockAreaOverlay.draw(canvas); - } - - // Draw the hint text - if (mHintTextAlpha > 0) { - Rect bounds = dockAreaOverlay.getBounds(); - int x = bounds.left + (bounds.width() - mHintTextBounds.x) / 2; - int y = bounds.top + (bounds.height() + mHintTextBounds.y) / 2; - mHintTextPaint.setAlpha(mHintTextAlpha); - if (hintTextOrientation == VERTICAL) { - canvas.save(); - canvas.rotate(-90f, bounds.centerX(), bounds.centerY()); - } - canvas.drawText(mHintText, x, y, mHintTextPaint); - if (hintTextOrientation == VERTICAL) { - canvas.restore(); - } - } - } - - /** - * Creates a new bounds and alpha animation. - */ - public void startAnimation(Rect bounds, int areaAlpha, int hintAlpha, int duration, - Interpolator interpolator, boolean animateAlpha, boolean animateBounds) { - if (mDockAreaOverlayAnimator != null) { - mDockAreaOverlayAnimator.cancel(); - } - - ObjectAnimator anim; - ArrayList<Animator> animators = new ArrayList<>(); - if (dockAreaOverlay.getAlpha() != areaAlpha) { - if (animateAlpha) { - anim = ObjectAnimator.ofInt(dockAreaOverlay, - Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), areaAlpha); - anim.setDuration(duration); - anim.setInterpolator(interpolator); - animators.add(anim); - } else { - dockAreaOverlay.setAlpha(areaAlpha); - } - } - if (mHintTextAlpha != hintAlpha) { - if (animateAlpha) { - anim = ObjectAnimator.ofInt(this, HINT_ALPHA, mHintTextAlpha, - hintAlpha); - anim.setDuration(150); - anim.setInterpolator(hintAlpha > mHintTextAlpha - ? Interpolators.ALPHA_IN - : Interpolators.ALPHA_OUT); - animators.add(anim); - } else { - mHintTextAlpha = hintAlpha; - dockAreaOverlay.invalidateSelf(); - } - } - if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) { - if (animateBounds) { - PropertyValuesHolder prop = PropertyValuesHolder.ofObject( - Utilities.DRAWABLE_RECT, Utilities.RECT_EVALUATOR, - new Rect(dockAreaOverlay.getBounds()), bounds); - anim = ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop); - anim.setDuration(duration); - anim.setInterpolator(interpolator); - animators.add(anim); - } else { - dockAreaOverlay.setBounds(bounds); - } - } - if (!animators.isEmpty()) { - mDockAreaOverlayAnimator = new AnimatorSet(); - mDockAreaOverlayAnimator.playTogether(animators); - mDockAreaOverlayAnimator.start(); - } - } - } - - public final int dockSide; - public final int createMode; - public final ViewState viewState; - private final RectF touchArea; - private final RectF dockArea; - private final RectF expandedTouchDockArea; - private static final Rect mTmpRect = new Rect(); - - /** - * @param createMode used to pass to ActivityManager to dock the task - * @param touchArea the area in which touch will initiate this dock state - * @param dockArea the visible dock area - * @param expandedTouchDockArea the area in which touch will continue to dock after entering - * the initial touch area. This is also the new dock area to - * draw. - */ - DockState(int dockSide, int createMode, int dockAreaAlpha, int hintTextAlpha, - @TextOrientation int hintTextOrientation, RectF touchArea, RectF dockArea, - RectF expandedTouchDockArea) { - this.dockSide = dockSide; - this.createMode = createMode; - this.viewState = new ViewState(dockAreaAlpha, hintTextAlpha, hintTextOrientation, - R.string.recents_drag_hint_message); - this.dockArea = dockArea; - this.touchArea = touchArea; - this.expandedTouchDockArea = expandedTouchDockArea; - } - - /** - * Updates the dock state with the given context. - */ - public void update(Context context) { - viewState.update(context); - } - - /** - * Returns the docked task bounds with the given {@param width} and {@param height}. - */ - public Rect getPreDockedBounds(int width, int height, Rect insets) { - getMappedRect(dockArea, width, height, mTmpRect); - return updateBoundsWithSystemInsets(mTmpRect, insets); - } - - /** - * Returns the expanded docked task bounds with the given {@param width} and - * {@param height}. - */ - public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets, - Resources res) { - // Calculate the docked task bounds - boolean isHorizontalDivision = - res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; - int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision, - insets, width, height, dividerSize); - Rect newWindowBounds = new Rect(); - DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds, - width, height, dividerSize); - return newWindowBounds; - } - - /** - * Returns the task stack bounds with the given {@param width} and - * {@param height}. - */ - public Rect getDockedTaskStackBounds(Rect displayRect, int width, int height, - int dividerSize, Rect insets, TaskStackLayoutAlgorithm layoutAlgorithm, - Resources res, Rect windowRectOut) { - // Calculate the inverse docked task bounds - boolean isHorizontalDivision = - res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; - int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision, - insets, width, height, dividerSize); - DockedDividerUtils.calculateBoundsForPosition(position, - DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height, - dividerSize); - - // Calculate the task stack bounds from the new window bounds - Rect taskStackBounds = new Rect(); - // If the task stack bounds is specifically under the dock area, then ignore the top - // inset - int top = dockArea.bottom < 1f - ? 0 - : insets.top; - // For now, ignore the left insets since we always dock on the left and show Recents - // on the right - layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right, - taskStackBounds); - return taskStackBounds; - } - - /** - * Returns the expanded bounds in certain dock sides such that the bounds account for the - * system insets (namely the vertical nav bar). This call modifies and returns the given - * {@param bounds}. - */ - private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) { - if (dockSide == DOCKED_LEFT) { - bounds.right += insets.left; - } else if (dockSide == DOCKED_RIGHT) { - bounds.left -= insets.right; - } - return bounds; - } - - /** - * Returns the mapped rect to the given dimensions. - */ - private void getMappedRect(RectF bounds, int width, int height, Rect out) { - out.set((int) (bounds.left * width), (int) (bounds.top * height), - (int) (bounds.right * width), (int) (bounds.bottom * height)); - } - } - - // A comparator that sorts tasks by their freeform state - private Comparator<Task> FREEFORM_COMPARATOR = new Comparator<Task>() { - @Override - public int compare(Task o1, Task o2) { - if (o1.isFreeformTask() && !o2.isFreeformTask()) { - return 1; - } else if (o2.isFreeformTask() && !o1.isFreeformTask()) { - return -1; - } - return Long.compare(o1.temporarySortIndexInStack, o2.temporarySortIndexInStack); - } - }; - - - // The task offset to apply to a task id as a group affiliation - static final int IndividualTaskIdOffset = 1 << 16; - - ArrayList<Task> mRawTaskList = new ArrayList<>(); - FilteredTaskList mStackTaskList = new FilteredTaskList(); - TaskStackCallbacks mCb; - - ArrayList<TaskGrouping> mGroups = new ArrayList<>(); - ArrayMap<Integer, TaskGrouping> mAffinitiesGroups = new ArrayMap<>(); - - public TaskStack() { - // Ensure that we only show non-docked tasks - mStackTaskList.setFilter(new TaskFilter() { - @Override - public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) { - if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) { - if (t.isAffiliatedTask()) { - // If this task is affiliated with another parent in the stack, then the - // historical state of this task depends on the state of the parent task - Task parentTask = taskIdMap.get(t.affiliationTaskId); - if (parentTask != null) { - t = parentTask; - } - } - } - return t.isStackTask; - } - }); - } - - /** Sets the callbacks for this task stack. */ - public void setCallbacks(TaskStackCallbacks cb) { - mCb = cb; - } - - /** - * Moves the given task to either the front of the freeform workspace or the stack. - */ - public void moveTaskToStack(Task task, int newStackId) { - // Find the index to insert into - ArrayList<Task> taskList = mStackTaskList.getTasks(); - int taskCount = taskList.size(); - if (!task.isFreeformTask() && (newStackId == FREEFORM_WORKSPACE_STACK_ID)) { - // Insert freeform tasks at the front - mStackTaskList.moveTaskToStack(task, taskCount, newStackId); - } else if (task.isFreeformTask() && (newStackId == FULLSCREEN_WORKSPACE_STACK_ID)) { - // Insert after the first stacked task - int insertIndex = 0; - for (int i = taskCount - 1; i >= 0; i--) { - if (!taskList.get(i).isFreeformTask()) { - insertIndex = i + 1; - break; - } - } - mStackTaskList.moveTaskToStack(task, insertIndex, newStackId); - } - } - - /** Does the actual work associated with removing the task. */ - void removeTaskImpl(FilteredTaskList taskList, Task t) { - // Remove the task from the list - taskList.remove(t); - // Remove it from the group as well, and if it is empty, remove the group - TaskGrouping group = t.group; - if (group != null) { - group.removeTask(t); - if (group.getTaskCount() == 0) { - removeGroup(group); - } - } - } - - /** - * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on - * how they should update themselves. - */ - public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) { - removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */); - } - - /** - * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on - * how they should update themselves. - */ - public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture, - boolean dismissRecentsIfAllRemoved) { - if (mStackTaskList.contains(t)) { - removeTaskImpl(mStackTaskList, t); - Task newFrontMostTask = getStackFrontMostTask(false /* includeFreeform */); - if (mCb != null) { - // Notify that a task has been removed - mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation, - fromDockGesture, dismissRecentsIfAllRemoved); - } - } - mRawTaskList.remove(t); - } - - /** - * Removes all tasks from the stack. - */ - public void removeAllTasks(boolean notifyStackChanges) { - ArrayList<Task> tasks = mStackTaskList.getTasks(); - for (int i = tasks.size() - 1; i >= 0; i--) { - Task t = tasks.get(i); - removeTaskImpl(mStackTaskList, t); - mRawTaskList.remove(t); - } - if (mCb != null && notifyStackChanges) { - // Notify that all tasks have been removed - mCb.onStackTasksRemoved(this); - } - } - - - /** - * @see #setTasks(Context, List, boolean, boolean) - */ - public void setTasks(Context context, TaskStack stack, boolean notifyStackChanges) { - setTasks(context, stack.mRawTaskList, notifyStackChanges); - } - - /** - * Sets a few tasks in one go, without calling any callbacks. - * - * @param tasks the new set of tasks to replace the current set. - * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks. - */ - public void setTasks(Context context, List<Task> tasks, boolean notifyStackChanges) { - // Compute a has set for each of the tasks - ArrayMap<Task.TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList); - ArrayMap<Task.TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks); - ArrayList<Task> addedTasks = new ArrayList<>(); - ArrayList<Task> removedTasks = new ArrayList<>(); - ArrayList<Task> allTasks = new ArrayList<>(); - - // Disable notifications if there are no callbacks - if (mCb == null) { - notifyStackChanges = false; - } - - // Remove any tasks that no longer exist - int taskCount = mRawTaskList.size(); - for (int i = taskCount - 1; i >= 0; i--) { - Task task = mRawTaskList.get(i); - if (!newTasksMap.containsKey(task.key)) { - if (notifyStackChanges) { - removedTasks.add(task); - } - } - task.setGroup(null); - } - - // Add any new tasks - taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task newTask = tasks.get(i); - Task currentTask = currentTasksMap.get(newTask.key); - if (currentTask == null && notifyStackChanges) { - addedTasks.add(newTask); - } else if (currentTask != null) { - // The current task has bound callbacks, so just copy the data from the new task - // state and add it back into the list - currentTask.copyFrom(newTask); - newTask = currentTask; - } - allTasks.add(newTask); - } - - // Sort all the tasks to ensure they are ordered correctly - for (int i = allTasks.size() - 1; i >= 0; i--) { - allTasks.get(i).temporarySortIndexInStack = i; - } - Collections.sort(allTasks, FREEFORM_COMPARATOR); - - mStackTaskList.set(allTasks); - mRawTaskList = allTasks; - - // Update the affiliated groupings - createAffiliatedGroupings(context); - - // Only callback for the removed tasks after the stack has updated - int removedTaskCount = removedTasks.size(); - Task newFrontMostTask = getStackFrontMostTask(false); - for (int i = 0; i < removedTaskCount; i++) { - mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask, - AnimationProps.IMMEDIATE, false /* fromDockGesture */, - true /* dismissRecentsIfAllRemoved */); - } - - // Only callback for the newly added tasks after this stack has been updated - int addedTaskCount = addedTasks.size(); - for (int i = 0; i < addedTaskCount; i++) { - mCb.onStackTaskAdded(this, addedTasks.get(i)); - } - - // Notify that the task stack has been updated - if (notifyStackChanges) { - mCb.onStackTasksUpdated(this); - } - } - - /** - * Gets the front-most task in the stack. - */ - public Task getStackFrontMostTask(boolean includeFreeformTasks) { - ArrayList<Task> stackTasks = mStackTaskList.getTasks(); - if (stackTasks.isEmpty()) { - return null; - } - for (int i = stackTasks.size() - 1; i >= 0; i--) { - Task task = stackTasks.get(i); - if (!task.isFreeformTask() || includeFreeformTasks) { - return task; - } - } - return null; - } - - /** Gets the task keys */ - public ArrayList<Task.TaskKey> getTaskKeys() { - ArrayList<Task.TaskKey> taskKeys = new ArrayList<>(); - ArrayList<Task> tasks = computeAllTasksList(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - taskKeys.add(task.key); - } - return taskKeys; - } - - /** - * Returns the set of "active" (non-historical) tasks in the stack that have been used recently. - */ - public ArrayList<Task> getStackTasks() { - return mStackTaskList.getTasks(); - } - - /** - * Returns the set of "freeform" tasks in the stack. - */ - public ArrayList<Task> getFreeformTasks() { - ArrayList<Task> freeformTasks = new ArrayList<>(); - ArrayList<Task> tasks = mStackTaskList.getTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - if (task.isFreeformTask()) { - freeformTasks.add(task); - } - } - return freeformTasks; - } - - /** - * Computes a set of all the active and historical tasks. - */ - public ArrayList<Task> computeAllTasksList() { - ArrayList<Task> tasks = new ArrayList<>(); - tasks.addAll(mStackTaskList.getTasks()); - return tasks; - } - - /** - * Returns the number of stack and freeform tasks. - */ - public int getTaskCount() { - return mStackTaskList.size(); - } - - /** - * Returns the number of stack tasks. - */ - public int getStackTaskCount() { - ArrayList<Task> tasks = mStackTaskList.getTasks(); - int stackCount = 0; - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - if (!task.isFreeformTask()) { - stackCount++; - } - } - return stackCount; - } - - /** - * Returns the number of freeform tasks. - */ - public int getFreeformTaskCount() { - ArrayList<Task> tasks = mStackTaskList.getTasks(); - int freeformCount = 0; - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - if (task.isFreeformTask()) { - freeformCount++; - } - } - return freeformCount; - } - - /** - * Returns the task in stack tasks which is the launch target. - */ - public Task getLaunchTarget() { - ArrayList<Task> tasks = mStackTaskList.getTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - if (task.isLaunchTarget) { - return task; - } - } - return null; - } - - /** - * Returns whether the next launch target should actually be the PiP task. - */ - public boolean isNextLaunchTargetPip(long lastPipTime) { - Task launchTarget = getLaunchTarget(); - Task nextLaunchTarget = getNextLaunchTargetRaw(); - if (nextLaunchTarget != null && lastPipTime > 0) { - // If the PiP time is more recent than the next launch target, then launch the PiP task - return lastPipTime > nextLaunchTarget.key.lastActiveTime; - } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) { - // Otherwise, if there is no next launch target, but there is a PiP, then launch - // the PiP task - return true; - } - return false; - } - - /** - * Returns the task in stack tasks which should be launched next if Recents are toggled - * again, or null if there is no task to be launched. Callers should check - * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the - * stack. - */ - public Task getNextLaunchTarget() { - Task nextLaunchTarget = getNextLaunchTargetRaw(); - if (nextLaunchTarget != null) { - return nextLaunchTarget; - } - return getStackTasks().get(getTaskCount() - 1); - } - - private Task getNextLaunchTargetRaw() { - int taskCount = getTaskCount(); - if (taskCount == 0) { - return null; - } - int launchTaskIndex = indexOfStackTask(getLaunchTarget()); - if (launchTaskIndex != -1 && launchTaskIndex > 0) { - return getStackTasks().get(launchTaskIndex - 1); - } - return null; - } - - /** Returns the index of this task in this current task stack */ - public int indexOfStackTask(Task t) { - return mStackTaskList.indexOf(t); - } - - /** Finds the task with the specified task id. */ - public Task findTaskWithId(int taskId) { - ArrayList<Task> tasks = computeAllTasksList(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - if (task.key.id == taskId) { - return task; - } - } - return null; - } - - /******** Grouping ********/ - - /** Adds a group to the set */ - public void addGroup(TaskGrouping group) { - mGroups.add(group); - mAffinitiesGroups.put(group.affiliation, group); - } - - public void removeGroup(TaskGrouping group) { - mGroups.remove(group); - mAffinitiesGroups.remove(group.affiliation); - } - - /** Returns the group with the specified affiliation. */ - public TaskGrouping getGroupWithAffiliation(int affiliation) { - return mAffinitiesGroups.get(affiliation); - } - - /** - * Temporary: This method will simulate affiliation groups - */ - void createAffiliatedGroupings(Context context) { - mGroups.clear(); - mAffinitiesGroups.clear(); - - if (RecentsDebugFlags.Static.EnableMockTaskGroups) { - ArrayMap<Task.TaskKey, Task> taskMap = new ArrayMap<>(); - // Sort all tasks by increasing firstActiveTime of the task - ArrayList<Task> tasks = mStackTaskList.getTasks(); - Collections.sort(tasks, new Comparator<Task>() { - @Override - public int compare(Task task, Task task2) { - return Long.compare(task.key.firstActiveTime, task2.key.firstActiveTime); - } - }); - // Create groups when sequential packages are the same - NamedCounter counter = new NamedCounter("task-group", ""); - int taskCount = tasks.size(); - String prevPackage = ""; - int prevAffiliation = -1; - Random r = new Random(); - int groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount; - for (int i = 0; i < taskCount; i++) { - Task t = tasks.get(i); - String packageName = t.key.getComponent().getPackageName(); - packageName = "pkg"; - TaskGrouping group; - if (packageName.equals(prevPackage) && groupCountDown > 0) { - group = getGroupWithAffiliation(prevAffiliation); - groupCountDown--; - } else { - int affiliation = IndividualTaskIdOffset + t.key.id; - group = new TaskGrouping(affiliation); - addGroup(group); - prevAffiliation = affiliation; - prevPackage = packageName; - groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount; - } - group.addTask(t); - taskMap.put(t.key, t); - } - // Sort groups by increasing latestActiveTime of the group - Collections.sort(mGroups, new Comparator<TaskGrouping>() { - @Override - public int compare(TaskGrouping taskGrouping, TaskGrouping taskGrouping2) { - return Long.compare(taskGrouping.latestActiveTimeInGroup, - taskGrouping2.latestActiveTimeInGroup); - } - }); - // Sort group tasks by increasing firstActiveTime of the task, and also build a new list - // of tasks - int taskIndex = 0; - int groupCount = mGroups.size(); - for (int i = 0; i < groupCount; i++) { - TaskGrouping group = mGroups.get(i); - Collections.sort(group.mTaskKeys, new Comparator<Task.TaskKey>() { - @Override - public int compare(Task.TaskKey taskKey, Task.TaskKey taskKey2) { - return Long.compare(taskKey.firstActiveTime, taskKey2.firstActiveTime); - } - }); - ArrayList<Task.TaskKey> groupTasks = group.mTaskKeys; - int groupTaskCount = groupTasks.size(); - for (int j = 0; j < groupTaskCount; j++) { - tasks.set(taskIndex, taskMap.get(groupTasks.get(j))); - taskIndex++; - } - } - mStackTaskList.set(tasks); - } else { - // Create the task groups - ArrayMap<Task.TaskKey, Task> tasksMap = new ArrayMap<>(); - ArrayList<Task> tasks = mStackTaskList.getTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task t = tasks.get(i); - TaskGrouping group; - if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) { - int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId : - IndividualTaskIdOffset + t.key.id; - if (mAffinitiesGroups.containsKey(affiliation)) { - group = getGroupWithAffiliation(affiliation); - } else { - group = new TaskGrouping(affiliation); - addGroup(group); - } - } else { - group = new TaskGrouping(t.key.id); - addGroup(group); - } - group.addTask(t); - tasksMap.put(t.key, t); - } - // Update the task colors for each of the groups - float minAlpha = context.getResources().getFloat( - R.dimen.recents_task_affiliation_color_min_alpha_percentage); - int taskGroupCount = mGroups.size(); - for (int i = 0; i < taskGroupCount; i++) { - TaskGrouping group = mGroups.get(i); - taskCount = group.getTaskCount(); - // Ignore the groups that only have one task - if (taskCount <= 1) continue; - // Calculate the group color distribution - int affiliationColor = tasksMap.get(group.mTaskKeys.get(0)).affiliationColor; - float alphaStep = (1f - minAlpha) / taskCount; - float alpha = 1f; - for (int j = 0; j < taskCount; j++) { - Task t = tasksMap.get(group.mTaskKeys.get(j)); - t.colorPrimary = Utilities.getColorWithOverlay(affiliationColor, Color.WHITE, - alpha); - alpha -= alphaStep; - } - } - } - } - - /** - * Computes the components of tasks in this stack that have been removed as a result of a change - * in the specified package. - */ - public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) { - // Identify all the tasks that should be removed as a result of the package being removed. - // Using a set to ensure that we callback once per unique component. - SystemServicesProxy ssp = Recents.getSystemServices(); - ArraySet<ComponentName> existingComponents = new ArraySet<>(); - ArraySet<ComponentName> removedComponents = new ArraySet<>(); - ArrayList<Task.TaskKey> taskKeys = getTaskKeys(); - int taskKeyCount = taskKeys.size(); - for (int i = 0; i < taskKeyCount; i++) { - Task.TaskKey t = taskKeys.get(i); - - // Skip if this doesn't apply to the current user - if (t.userId != userId) continue; - - ComponentName cn = t.getComponent(); - if (cn.getPackageName().equals(packageName)) { - if (existingComponents.contains(cn)) { - // If we know that the component still exists in the package, then skip - continue; - } - if (ssp.getActivityInfo(cn, userId) != null) { - existingComponents.add(cn); - } else { - removedComponents.add(cn); - } - } - } - return removedComponents; - } - - @Override - public String toString() { - String str = "Stack Tasks (" + mStackTaskList.size() + "):\n"; - ArrayList<Task> tasks = mStackTaskList.getTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - str += " " + tasks.get(i).toString() + "\n"; - } - return str; - } - - /** - * Given a list of tasks, returns a map of each task's key to the task. - */ - private ArrayMap<Task.TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) { - ArrayMap<Task.TaskKey, Task> map = new ArrayMap<>(tasks.size()); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - map.put(task.key, task); - } - return map; - } - - public void dump(String prefix, PrintWriter writer) { - String innerPrefix = prefix + " "; - - writer.print(prefix); writer.print(TAG); - writer.print(" numStackTasks="); writer.print(mStackTaskList.size()); - writer.println(); - ArrayList<Task> tasks = mStackTaskList.getTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - tasks.get(i).dump(innerPrefix, writer); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java deleted file mode 100644 index 33ff1b634d64..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.model; - -import android.app.ActivityManager.TaskSnapshot; -import android.graphics.Bitmap; -import android.graphics.Rect; - -/** - * Data for a single thumbnail. - */ -public class ThumbnailData { - - // TODO: Make these final once the non-snapshot path is removed. - public Bitmap thumbnail; - public int orientation; - public final Rect insets = new Rect(); - public boolean reducedResolution; - public float scale; - - public static ThumbnailData createFromTaskSnapshot(TaskSnapshot snapshot) { - ThumbnailData out = new ThumbnailData(); - out.thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot()); - out.insets.set(snapshot.getContentInsets()); - out.orientation = snapshot.getOrientation(); - out.reducedResolution = snapshot.isReducedResolution(); - out.scale = snapshot.getScale(); - return out; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java deleted file mode 100644 index dba085e37427..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.views; - -import android.graphics.Outline; -import android.graphics.Rect; -import android.view.View; -import android.view.ViewDebug; -import android.view.ViewOutlineProvider; - -import com.android.systemui.recents.misc.Utilities; - -/* An outline provider that has a clip and outline that can be animated. */ -public class AnimateableViewBounds extends ViewOutlineProvider { - - private static final float MIN_ALPHA = 0.1f; - private static final float MAX_ALPHA = 0.8f; - - protected View mSourceView; - @ViewDebug.ExportedProperty(category="recents") - protected Rect mClipRect = new Rect(); - @ViewDebug.ExportedProperty(category="recents") - protected Rect mClipBounds = new Rect(); - @ViewDebug.ExportedProperty(category="recents") - protected Rect mLastClipBounds = new Rect(); - @ViewDebug.ExportedProperty(category="recents") - protected int mCornerRadius; - @ViewDebug.ExportedProperty(category="recents") - protected float mAlpha = 1f; - - public AnimateableViewBounds(View source, int cornerRadius) { - mSourceView = source; - mCornerRadius = cornerRadius; - } - - /** - * Resets the right and bottom clip for this view. - */ - public void reset() { - mClipRect.set(-1, -1, -1, -1); - updateClipBounds(); - } - - @Override - public void getOutline(View view, Outline outline) { - outline.setAlpha(Utilities.mapRange(mAlpha, MIN_ALPHA, MAX_ALPHA)); - if (mCornerRadius > 0) { - outline.setRoundRect(mClipRect.left, mClipRect.top, - mSourceView.getWidth() - mClipRect.right, - mSourceView.getHeight() - mClipRect.bottom, - mCornerRadius); - } else { - outline.setRect(mClipRect.left, mClipRect.top, - mSourceView.getWidth() - mClipRect.right, - mSourceView.getHeight() - mClipRect.bottom); - } - } - - /** - * Sets the view outline alpha. - */ - void setAlpha(float alpha) { - if (Float.compare(alpha, mAlpha) != 0) { - mAlpha = alpha; - // TODO, If both clip and alpha change in the same frame, only invalidate once - mSourceView.invalidateOutline(); - } - } - - /** - * @return the outline alpha. - */ - public float getAlpha() { - return mAlpha; - } - - /** Sets the top clip. */ - public void setClipTop(int top) { - mClipRect.top = top; - updateClipBounds(); - } - - /** Returns the top clip. */ - public int getClipTop() { - return mClipRect.top; - } - - /** Sets the bottom clip. */ - public void setClipBottom(int bottom) { - mClipRect.bottom = bottom; - updateClipBounds(); - } - - /** Returns the bottom clip. */ - public int getClipBottom() { - return mClipRect.bottom; - } - - protected void updateClipBounds() { - mClipBounds.set(Math.max(0, mClipRect.left), Math.max(0, mClipRect.top), - mSourceView.getWidth() - Math.max(0, mClipRect.right), - mSourceView.getHeight() - Math.max(0, mClipRect.bottom)); - if (!mLastClipBounds.equals(mClipBounds)) { - mSourceView.setClipBounds(mClipBounds); - // TODO, If both clip and alpha change in the same frame, only invalidate once - mSourceView.invalidateOutline(); - mLastClipBounds.set(mClipBounds); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java deleted file mode 100644 index 716d1bcf78c2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.views; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ValueAnimator; -import android.annotation.IntDef; -import android.util.SparseArray; -import android.util.SparseLongArray; -import android.view.View; -import android.view.animation.Interpolator; - -import com.android.systemui.Interpolators; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.List; - -/** - * The generic set of animation properties to animate a {@link View}. The animation can have - * different interpolators, start delays and durations for each of the different properties. - */ -public class AnimationProps { - - public static final AnimationProps IMMEDIATE = new AnimationProps(0, Interpolators.LINEAR); - - @Retention(RetentionPolicy.SOURCE) - @IntDef({ALL, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, ALPHA, SCALE, BOUNDS}) - public @interface PropType {} - - public static final int ALL = 0; - public static final int TRANSLATION_X = 1; - public static final int TRANSLATION_Y = 2; - public static final int TRANSLATION_Z = 3; - public static final int ALPHA = 4; - public static final int SCALE = 5; - public static final int BOUNDS = 6; - public static final int DIM_ALPHA = 7; - public static final int FOCUS_STATE = 8; - - private SparseLongArray mPropStartDelay; - private SparseLongArray mPropDuration; - private SparseArray<Interpolator> mPropInterpolators; - private Animator.AnimatorListener mListener; - - /** - * The builder constructor. - */ - public AnimationProps() {} - - /** - * Creates an animation with a default {@param duration} and {@param interpolator} for all - * properties in this animation. - */ - public AnimationProps(int duration, Interpolator interpolator) { - this(0, duration, interpolator, null); - } - - /** - * Creates an animation with a default {@param duration} and {@param interpolator} for all - * properties in this animation. - */ - public AnimationProps(int duration, Interpolator interpolator, - Animator.AnimatorListener listener) { - this(0, duration, interpolator, listener); - } - - /** - * Creates an animation with a default {@param startDelay}, {@param duration} and - * {@param interpolator} for all properties in this animation. - */ - public AnimationProps(int startDelay, int duration, Interpolator interpolator) { - this(startDelay, duration, interpolator, null); - } - - /** - * Creates an animation with a default {@param startDelay}, {@param duration} and - * {@param interpolator} for all properties in this animation. - */ - public AnimationProps(int startDelay, int duration, Interpolator interpolator, - Animator.AnimatorListener listener) { - setStartDelay(ALL, startDelay); - setDuration(ALL, duration); - setInterpolator(ALL, interpolator); - setListener(listener); - } - - /** - * Creates a new {@link AnimatorSet} that will animate the given animators. Callers need to - * manually apply the individual animation properties for each of the animators respectively. - */ - public AnimatorSet createAnimator(List<Animator> animators) { - AnimatorSet anim = new AnimatorSet(); - if (mListener != null) { - anim.addListener(mListener); - } - anim.playTogether(animators); - return anim; - } - - /** - * Applies the specific start delay, duration and interpolator to the given {@param animator} - * for the specified {@param propertyType}. - */ - public <T extends ValueAnimator> T apply(@PropType int propertyType, T animator) { - animator.setStartDelay(getStartDelay(propertyType)); - animator.setDuration(getDuration(propertyType)); - animator.setInterpolator(getInterpolator(propertyType)); - return animator; - } - - /** - * Sets a start delay for a specific property. - */ - public AnimationProps setStartDelay(@PropType int propertyType, int startDelay) { - if (mPropStartDelay == null) { - mPropStartDelay = new SparseLongArray(); - } - mPropStartDelay.append(propertyType, startDelay); - return this; - } - - /** - * Returns the start delay for a specific property. - */ - public long getStartDelay(@PropType int propertyType) { - if (mPropStartDelay != null) { - long startDelay = mPropStartDelay.get(propertyType, -1); - if (startDelay != -1) { - return startDelay; - } - return mPropStartDelay.get(ALL, 0); - } - return 0; - } - - /** - * Sets a duration for a specific property. - */ - public AnimationProps setDuration(@PropType int propertyType, int duration) { - if (mPropDuration == null) { - mPropDuration = new SparseLongArray(); - } - mPropDuration.append(propertyType, duration); - return this; - } - - /** - * Returns the duration for a specific property. - */ - public long getDuration(@PropType int propertyType) { - if (mPropDuration != null) { - long duration = mPropDuration.get(propertyType, -1); - if (duration != -1) { - return duration; - } - return mPropDuration.get(ALL, 0); - } - return 0; - } - - /** - * Sets an interpolator for a specific property. - */ - public AnimationProps setInterpolator(@PropType int propertyType, Interpolator interpolator) { - if (mPropInterpolators == null) { - mPropInterpolators = new SparseArray<>(); - } - mPropInterpolators.append(propertyType, interpolator); - return this; - } - - /** - * Returns the interpolator for a specific property, falling back to the general interpolator - * if there is no specific property interpolator. - */ - public Interpolator getInterpolator(@PropType int propertyType) { - if (mPropInterpolators != null) { - Interpolator interp = mPropInterpolators.get(propertyType); - if (interp != null) { - return interp; - } - return mPropInterpolators.get(ALL, Interpolators.LINEAR); - } - return Interpolators.LINEAR; - } - - /** - * Sets an animator listener for this animation. - */ - public AnimationProps setListener(Animator.AnimatorListener listener) { - mListener = listener; - return this; - } - - /** - * Returns the animator listener for this animation. - */ - public Animator.AnimatorListener getListener() { - return mListener; - } - - /** - * Returns whether this animation has any duration. - */ - public boolean isImmediate() { - int count = mPropDuration.size(); - for (int i = 0; i < count; i++) { - if (mPropDuration.valueAt(i) > 0) { - return false; - } - } - return true; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/DockState.java b/packages/SystemUI/src/com/android/systemui/recents/views/DockState.java new file mode 100644 index 000000000000..65b96fbb52f9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/DockState.java @@ -0,0 +1,351 @@ +/* + * 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.recents.views; + +import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT; +import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; +import static android.view.WindowManager.DOCKED_BOTTOM; +import static android.view.WindowManager.DOCKED_INVALID; +import static android.view.WindowManager.DOCKED_LEFT; +import static android.view.WindowManager.DOCKED_RIGHT; +import static android.view.WindowManager.DOCKED_TOP; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.annotation.IntDef; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.ColorDrawable; +import android.util.IntProperty; +import android.view.animation.Interpolator; + +import com.android.internal.policy.DockedDividerUtils; +import com.android.systemui.Interpolators; +import com.android.systemui.R; +import com.android.systemui.recents.Recents; +import com.android.systemui.shared.recents.utilities.Utilities; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; + +/** + * The various possible dock states when dragging and dropping a task. + */ +public class DockState implements DropTarget { + + public static final int DOCK_AREA_BG_COLOR = 0xFFffffff; + public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000; + + // The rotation to apply to the hint text + @Retention(RetentionPolicy.SOURCE) + @IntDef({HORIZONTAL, VERTICAL}) + public @interface TextOrientation {} + private static final int HORIZONTAL = 0; + private static final int VERTICAL = 1; + + private static final int DOCK_AREA_ALPHA = 80; + public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL, + null, null, null); + public static final DockState LEFT = new DockState(DOCKED_LEFT, + SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL, + new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1), + new RectF(0, 0, 0.5f, 1)); + public static final DockState TOP = new DockState(DOCKED_TOP, + SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL, + new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f), + new RectF(0, 0, 1, 0.5f)); + public static final DockState RIGHT = new DockState(DOCKED_RIGHT, + SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL, + new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1), + new RectF(0.5f, 0, 1, 1)); + public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM, + SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL, + new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1), + new RectF(0, 0.5f, 1, 1)); + + @Override + public boolean acceptsDrop(int x, int y, int width, int height, Rect insets, + boolean isCurrentTarget) { + if (isCurrentTarget) { + getMappedRect(expandedTouchDockArea, width, height, mTmpRect); + return mTmpRect.contains(x, y); + } else { + getMappedRect(touchArea, width, height, mTmpRect); + updateBoundsWithSystemInsets(mTmpRect, insets); + return mTmpRect.contains(x, y); + } + } + + // Represents the view state of this dock state + public static class ViewState { + private static final IntProperty<ViewState> HINT_ALPHA = + new IntProperty<ViewState>("drawableAlpha") { + @Override + public void setValue(ViewState object, int alpha) { + object.mHintTextAlpha = alpha; + object.dockAreaOverlay.invalidateSelf(); + } + + @Override + public Integer get(ViewState object) { + return object.mHintTextAlpha; + } + }; + + public final int dockAreaAlpha; + public final ColorDrawable dockAreaOverlay; + public final int hintTextAlpha; + public final int hintTextOrientation; + + private final int mHintTextResId; + private String mHintText; + private Paint mHintTextPaint; + private Point mHintTextBounds = new Point(); + private int mHintTextAlpha = 255; + private AnimatorSet mDockAreaOverlayAnimator; + private Rect mTmpRect = new Rect(); + + private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation, + int hintTextResId) { + dockAreaAlpha = areaAlpha; + dockAreaOverlay = new ColorDrawable(Recents.getConfiguration().isGridEnabled + ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR); + dockAreaOverlay.setAlpha(0); + hintTextAlpha = hintAlpha; + hintTextOrientation = hintOrientation; + mHintTextResId = hintTextResId; + mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mHintTextPaint.setColor(Color.WHITE); + } + + /** + * Updates the view state with the given context. + */ + public void update(Context context) { + Resources res = context.getResources(); + mHintText = context.getString(mHintTextResId); + mHintTextPaint.setTextSize(res.getDimensionPixelSize( + R.dimen.recents_drag_hint_text_size)); + mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mTmpRect); + mHintTextBounds.set((int) mHintTextPaint.measureText(mHintText), mTmpRect.height()); + } + + /** + * Draws the current view state. + */ + public void draw(Canvas canvas) { + // Draw the overlay background + if (dockAreaOverlay.getAlpha() > 0) { + dockAreaOverlay.draw(canvas); + } + + // Draw the hint text + if (mHintTextAlpha > 0) { + Rect bounds = dockAreaOverlay.getBounds(); + int x = bounds.left + (bounds.width() - mHintTextBounds.x) / 2; + int y = bounds.top + (bounds.height() + mHintTextBounds.y) / 2; + mHintTextPaint.setAlpha(mHintTextAlpha); + if (hintTextOrientation == VERTICAL) { + canvas.save(); + canvas.rotate(-90f, bounds.centerX(), bounds.centerY()); + } + canvas.drawText(mHintText, x, y, mHintTextPaint); + if (hintTextOrientation == VERTICAL) { + canvas.restore(); + } + } + } + + /** + * Creates a new bounds and alpha animation. + */ + public void startAnimation(Rect bounds, int areaAlpha, int hintAlpha, int duration, + Interpolator interpolator, boolean animateAlpha, boolean animateBounds) { + if (mDockAreaOverlayAnimator != null) { + mDockAreaOverlayAnimator.cancel(); + } + + ObjectAnimator anim; + ArrayList<Animator> animators = new ArrayList<>(); + if (dockAreaOverlay.getAlpha() != areaAlpha) { + if (animateAlpha) { + anim = ObjectAnimator.ofInt(dockAreaOverlay, + Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), areaAlpha); + anim.setDuration(duration); + anim.setInterpolator(interpolator); + animators.add(anim); + } else { + dockAreaOverlay.setAlpha(areaAlpha); + } + } + if (mHintTextAlpha != hintAlpha) { + if (animateAlpha) { + anim = ObjectAnimator.ofInt(this, HINT_ALPHA, mHintTextAlpha, + hintAlpha); + anim.setDuration(150); + anim.setInterpolator(hintAlpha > mHintTextAlpha + ? Interpolators.ALPHA_IN + : Interpolators.ALPHA_OUT); + animators.add(anim); + } else { + mHintTextAlpha = hintAlpha; + dockAreaOverlay.invalidateSelf(); + } + } + if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) { + if (animateBounds) { + PropertyValuesHolder prop = PropertyValuesHolder.ofObject( + Utilities.DRAWABLE_RECT, Utilities.RECT_EVALUATOR, + new Rect(dockAreaOverlay.getBounds()), bounds); + anim = ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop); + anim.setDuration(duration); + anim.setInterpolator(interpolator); + animators.add(anim); + } else { + dockAreaOverlay.setBounds(bounds); + } + } + if (!animators.isEmpty()) { + mDockAreaOverlayAnimator = new AnimatorSet(); + mDockAreaOverlayAnimator.playTogether(animators); + mDockAreaOverlayAnimator.start(); + } + } + } + + public final int dockSide; + public final int createMode; + public final ViewState viewState; + private final RectF touchArea; + private final RectF dockArea; + private final RectF expandedTouchDockArea; + private static final Rect mTmpRect = new Rect(); + + /** + * @param createMode used to pass to ActivityManager to dock the task + * @param touchArea the area in which touch will initiate this dock state + * @param dockArea the visible dock area + * @param expandedTouchDockArea the area in which touch will continue to dock after entering + * the initial touch area. This is also the new dock area to + * draw. + */ + DockState(int dockSide, int createMode, int dockAreaAlpha, int hintTextAlpha, + @TextOrientation int hintTextOrientation, RectF touchArea, RectF dockArea, + RectF expandedTouchDockArea) { + this.dockSide = dockSide; + this.createMode = createMode; + this.viewState = new ViewState(dockAreaAlpha, hintTextAlpha, hintTextOrientation, + R.string.recents_drag_hint_message); + this.dockArea = dockArea; + this.touchArea = touchArea; + this.expandedTouchDockArea = expandedTouchDockArea; + } + + /** + * Updates the dock state with the given context. + */ + public void update(Context context) { + viewState.update(context); + } + + /** + * Returns the docked task bounds with the given {@param width} and {@param height}. + */ + public Rect getPreDockedBounds(int width, int height, Rect insets) { + getMappedRect(dockArea, width, height, mTmpRect); + return updateBoundsWithSystemInsets(mTmpRect, insets); + } + + /** + * Returns the expanded docked task bounds with the given {@param width} and + * {@param height}. + */ + public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets, + Resources res) { + // Calculate the docked task bounds + boolean isHorizontalDivision = + res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; + int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision, + insets, width, height, dividerSize); + Rect newWindowBounds = new Rect(); + DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds, + width, height, dividerSize); + return newWindowBounds; + } + + /** + * Returns the task stack bounds with the given {@param width} and + * {@param height}. + */ + public Rect getDockedTaskStackBounds(Rect displayRect, int width, int height, + int dividerSize, Rect insets, TaskStackLayoutAlgorithm layoutAlgorithm, + Resources res, Rect windowRectOut) { + // Calculate the inverse docked task bounds + boolean isHorizontalDivision = + res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; + int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision, + insets, width, height, dividerSize); + DockedDividerUtils.calculateBoundsForPosition(position, + DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height, + dividerSize); + + // Calculate the task stack bounds from the new window bounds + Rect taskStackBounds = new Rect(); + // If the task stack bounds is specifically under the dock area, then ignore the top + // inset + int top = dockArea.bottom < 1f + ? 0 + : insets.top; + // For now, ignore the left insets since we always dock on the left and show Recents + // on the right + layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right, + taskStackBounds); + return taskStackBounds; + } + + /** + * Returns the expanded bounds in certain dock sides such that the bounds account for the + * system insets (namely the vertical nav bar). This call modifies and returns the given + * {@param bounds}. + */ + private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) { + if (dockSide == DOCKED_LEFT) { + bounds.right += insets.left; + } else if (dockSide == DOCKED_RIGHT) { + bounds.left -= insets.right; + } + return bounds; + } + + /** + * Returns the mapped rect to the given dimensions. + */ + private void getMappedRect(RectF bounds, int width, int height, Rect out) { + out.set((int) (bounds.left * width), (int) (bounds.top * height), + (int) (bounds.right * width), (int) (bounds.bottom * height)); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java deleted file mode 100644 index 035c058c7e8d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.views; - -import android.content.Context; -import android.graphics.RectF; -import android.util.ArrayMap; - -import com.android.systemui.R; -import com.android.systemui.recents.model.Task; - -import java.util.Collections; -import java.util.List; - -/** - * The layout logic for the contents of the freeform workspace. - */ -public class FreeformWorkspaceLayoutAlgorithm { - - // Optimization, allows for quick lookup of task -> rect - private ArrayMap<Task.TaskKey, RectF> mTaskRectMap = new ArrayMap<>(); - - private int mTaskPadding; - - public FreeformWorkspaceLayoutAlgorithm(Context context) { - reloadOnConfigurationChange(context); - } - - /** - * Reloads the layout for the current configuration. - */ - public void reloadOnConfigurationChange(Context context) { - // This is applied to the edges of each task - mTaskPadding = context.getResources().getDimensionPixelSize( - R.dimen.recents_freeform_layout_task_padding) / 2; - } - - /** - * Updates the layout for each of the freeform workspace tasks. This is called after the stack - * layout is updated. - */ - public void update(List<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) { - Collections.reverse(freeformTasks); - mTaskRectMap.clear(); - - int numFreeformTasks = stackLayout.mNumFreeformTasks; - if (!freeformTasks.isEmpty()) { - - // Normalize the widths so that we can calculate the best layout below - int workspaceWidth = stackLayout.mFreeformRect.width(); - int workspaceHeight = stackLayout.mFreeformRect.height(); - float normalizedWorkspaceWidth = (float) workspaceWidth / workspaceHeight; - float normalizedWorkspaceHeight = 1f; - float[] normalizedTaskWidths = new float[numFreeformTasks]; - for (int i = 0; i < numFreeformTasks; i++) { - Task task = freeformTasks.get(i); - float rowTaskWidth; - if (task.bounds != null) { - rowTaskWidth = (float) task.bounds.width() / task.bounds.height(); - } else { - // If this is a stack task that was dragged into the freeform workspace, then - // the task will not yet have an associated bounds, so assume the full workspace - // width for the time being - rowTaskWidth = normalizedWorkspaceWidth; - } - // Bound the task width to the workspace width so that at the worst case, it will - // fit its own row - normalizedTaskWidths[i] = Math.min(rowTaskWidth, normalizedWorkspaceWidth); - } - - // Determine the scale to best fit each of the tasks in the workspace - float rowScale = 0.85f; - float rowWidth = 0f; - float maxRowWidth = 0f; - int rowCount = 1; - for (int i = 0; i < numFreeformTasks;) { - float width = normalizedTaskWidths[i] * rowScale; - if (rowWidth + width > normalizedWorkspaceWidth) { - // That is too long for this row, create new row - if ((rowCount + 1) * rowScale > normalizedWorkspaceHeight) { - // The new row is too high, so we need to try fitting again. Update the - // scale to be the smaller of the scale needed to fit the task in the - // previous row, or the scale needed to fit the new row - rowScale = Math.min(normalizedWorkspaceWidth / (rowWidth + width), - normalizedWorkspaceHeight / (rowCount + 1)); - rowCount = 1; - rowWidth = 0; - i = 0; - } else { - // The new row fits, so continue - rowWidth = width; - rowCount++; - i++; - } - } else { - // Task is OK in this row - rowWidth += width; - i++; - } - maxRowWidth = Math.max(rowWidth, maxRowWidth); - } - - // Normalize each of the actual rects to that scale - float defaultRowLeft = ((1f - (maxRowWidth / normalizedWorkspaceWidth)) * - workspaceWidth) / 2f; - float rowLeft = defaultRowLeft; - float rowTop = ((1f - (rowScale * rowCount)) * workspaceHeight) / 2f; - float rowHeight = rowScale * workspaceHeight; - for (int i = 0; i < numFreeformTasks; i++) { - Task task = freeformTasks.get(i); - float width = rowHeight * normalizedTaskWidths[i]; - if (rowLeft + width > workspaceWidth) { - // This goes on the next line - rowTop += rowHeight; - rowLeft = defaultRowLeft; - } - RectF rect = new RectF(rowLeft, rowTop, rowLeft + width, rowTop + rowHeight); - rect.inset(mTaskPadding, mTaskPadding); - rowLeft += width; - mTaskRectMap.put(task.key, rect); - } - } - } - - /** - * Returns whether the transform is available for the given task. - */ - public boolean isTransformAvailable(Task task, TaskStackLayoutAlgorithm stackLayout) { - if (stackLayout.mNumFreeformTasks == 0 || task == null) { - return false; - } - return mTaskRectMap.containsKey(task.key); - } - - /** - * Returns the transform for the given task. Any rect returned will be offset by the actual - * transform for the freeform workspace. - */ - public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut, - TaskStackLayoutAlgorithm stackLayout) { - if (mTaskRectMap.containsKey(task.key)) { - final RectF ffRect = mTaskRectMap.get(task.key); - - transformOut.scale = 1f; - transformOut.alpha = 1f; - transformOut.translationZ = stackLayout.mMaxTranslationZ; - transformOut.dimAlpha = 0f; - transformOut.viewOutlineAlpha = TaskStackLayoutAlgorithm.OUTLINE_ALPHA_MAX_VALUE; - transformOut.rect.set(ffRect); - transformOut.rect.offset(stackLayout.mFreeformRect.left, stackLayout.mFreeformRect.top); - transformOut.visible = true; - return transformOut; - } - return null; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionComposer.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionComposer.java new file mode 100644 index 000000000000..1c47430106e8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionComposer.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recents.views; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.GraphicBuffer; +import android.graphics.Rect; +import android.util.Log; + +import com.android.systemui.recents.Recents; +import com.android.systemui.recents.RecentsDebugFlags; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat; +import com.android.systemui.shared.recents.view.RecentsTransition; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A helper class to create the transition app animation specs to/from Recents + */ +public class RecentsTransitionComposer { + + private static final String TAG = "RecentsTransitionComposer"; + + private Context mContext; + private TaskViewTransform mTmpTransform = new TaskViewTransform(); + + public RecentsTransitionComposer(Context context) { + mContext = context; + } + + /** + * Composes a single animation spec for the given {@link TaskView} + */ + private static AppTransitionAnimationSpecCompat composeAnimationSpec(TaskStackView stackView, + TaskView taskView, TaskViewTransform transform, boolean addHeaderBitmap) { + Bitmap b = null; + if (addHeaderBitmap) { + b = composeHeaderBitmap(taskView, transform); + if (b == null) { + return null; + } + } + + Rect taskRect = new Rect(); + transform.rect.round(taskRect); + // Disable in for low ram devices because each task does in Recents does not have fullscreen + // height (stackView height) and when transitioning to fullscreen app, the code below would + // force the task thumbnail to full stackView height immediately causing the transition + // jarring. + if (!Recents.getConfiguration().isLowRamDevice && taskView.getTask() != + stackView.getStack().getFrontMostTask()) { + taskRect.bottom = taskRect.top + stackView.getMeasuredHeight(); + } + return new AppTransitionAnimationSpecCompat(taskView.getTask().key.id, b, taskRect); + } + + /** + * Composes the transition spec when docking a task, which includes a full task bitmap. + */ + public List<AppTransitionAnimationSpecCompat> composeDockAnimationSpec(TaskView taskView, + Rect bounds) { + mTmpTransform.fillIn(taskView); + Task task = taskView.getTask(); + Bitmap buffer = RecentsTransitionComposer.composeTaskBitmap(taskView, mTmpTransform); + return Collections.singletonList(new AppTransitionAnimationSpecCompat(task.key.id, buffer, + bounds)); + } + + /** + * Composes the animation specs for all the tasks in the target stack. + */ + public List<AppTransitionAnimationSpecCompat> composeAnimationSpecs(final Task task, + final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) { + // Calculate the offscreen task rect (for tasks that are not backed by views) + TaskView taskView = stackView.getChildViewForTask(task); + TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm(); + Rect offscreenTaskRect = new Rect(); + stackLayout.getFrontOfStackTransform().rect.round(offscreenTaskRect); + + // If this is a full screen stack, the transition will be towards the single, full screen + // task. We only need the transition spec for this task. + + // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to + // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED) + if (windowingMode == WINDOWING_MODE_FULLSCREEN + || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY + || activityType == ACTIVITY_TYPE_ASSISTANT + || windowingMode == WINDOWING_MODE_UNDEFINED) { + List<AppTransitionAnimationSpecCompat> specs = new ArrayList<>(); + if (taskView == null) { + specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect)); + } else { + mTmpTransform.fillIn(taskView); + stackLayout.transformToScreenCoordinates(mTmpTransform, windowRect); + AppTransitionAnimationSpecCompat spec = composeAnimationSpec(stackView, taskView, + mTmpTransform, true /* addHeaderBitmap */); + if (spec != null) { + specs.add(spec); + } + } + return specs; + } + return Collections.emptyList(); + } + + /** + * Composes a single animation spec for the given {@link Task} + */ + private static AppTransitionAnimationSpecCompat composeOffscreenAnimationSpec(Task task, + Rect taskRect) { + return new AppTransitionAnimationSpecCompat(task.key.id, null, taskRect); + } + + public static Bitmap composeTaskBitmap(TaskView taskView, TaskViewTransform transform) { + float scale = transform.scale; + int fromWidth = (int) (transform.rect.width() * scale); + int fromHeight = (int) (transform.rect.height() * scale); + if (fromWidth == 0 || fromHeight == 0) { + Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() + + " at transform: " + transform); + + return RecentsTransition.drawViewIntoHardwareBitmap(1, 1, null, 1f, 0x00ffffff); + } else { + if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) { + return RecentsTransition.drawViewIntoHardwareBitmap(fromWidth, fromHeight, null, 1f, + 0xFFff0000); + } else { + return RecentsTransition.drawViewIntoHardwareBitmap(fromWidth, fromHeight, taskView, + scale, 0); + } + } + } + + private static Bitmap composeHeaderBitmap(TaskView taskView, + TaskViewTransform transform) { + float scale = transform.scale; + int headerWidth = (int) (transform.rect.width()); + int headerHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale); + if (headerWidth == 0 || headerHeight == 0) { + return null; + } + + if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) { + return RecentsTransition.drawViewIntoHardwareBitmap(headerWidth, headerHeight, null, 1f, + 0xFFff0000); + } else { + return RecentsTransition.drawViewIntoHardwareBitmap(headerWidth, headerHeight, + taskView.mHeaderView, scale, 0); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java deleted file mode 100644 index b2675d7ac858..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java +++ /dev/null @@ -1,498 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.views; - -import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID; -import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; -import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; -import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; -import static android.app.ActivityManager.StackId.INVALID_STACK_ID; - -import android.annotation.Nullable; -import android.app.ActivityManager.StackId; -import android.app.ActivityOptions; -import android.app.ActivityOptions.OnAnimationStartedListener; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.GraphicBuffer; -import android.graphics.Rect; -import android.os.Bundle; -import android.os.Handler; -import android.os.IRemoteCallback; -import android.os.RemoteException; -import android.util.Log; -import android.view.AppTransitionAnimationSpec; -import android.view.DisplayListCanvas; -import android.view.IAppTransitionAnimationSpecsFuture; -import android.view.RenderNode; -import android.view.ThreadedRenderer; -import android.view.View; - -import com.android.internal.annotations.GuardedBy; -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.RecentsDebugFlags; -import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent; -import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent; -import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent; -import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent; -import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent; -import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; -import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent; -import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.TaskStack; -import com.android.systemui.statusbar.phone.StatusBar; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * A helper class to create transitions to/from Recents - */ -public class RecentsTransitionHelper { - - private static final String TAG = "RecentsTransitionHelper"; - private static final boolean DEBUG = false; - - /** - * Special value for {@link #mAppTransitionAnimationSpecs}: Indicate that we are currently - * waiting for the specs to be retrieved. - */ - private static final List<AppTransitionAnimationSpec> SPECS_WAITING = new ArrayList<>(); - - @GuardedBy("this") - private List<AppTransitionAnimationSpec> mAppTransitionAnimationSpecs = SPECS_WAITING; - - private Context mContext; - private Handler mHandler; - private TaskViewTransform mTmpTransform = new TaskViewTransform(); - - private class StartScreenPinningRunnableRunnable implements Runnable { - - private int taskId = -1; - - @Override - public void run() { - EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext, taskId)); - } - } - private StartScreenPinningRunnableRunnable mStartScreenPinningRunnable - = new StartScreenPinningRunnableRunnable(); - - public RecentsTransitionHelper(Context context) { - mContext = context; - mHandler = new Handler(); - } - - /** - * Launches the specified {@link Task}. - */ - public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task, - final TaskStackView stackView, final TaskView taskView, - final boolean screenPinningRequested, final int destinationStack) { - - final ActivityOptions.OnAnimationStartedListener animStartedListener; - final AppTransitionAnimationSpecsFuture transitionFuture; - if (taskView != null) { - - // Fetch window rect here already in order not to be blocked on lock contention in WM - // when the future calls it. - final Rect windowRect = Recents.getSystemServices().getWindowRect(); - transitionFuture = getAppTransitionFuture( - () -> composeAnimationSpecs(task, stackView, destinationStack, windowRect)); - animStartedListener = new OnAnimationStartedListener() { - private boolean mHandled; - - @Override - public void onAnimationStarted() { - if (mHandled) { - return; - } - mHandled = true; - - // If we are launching into another task, cancel the previous task's - // window transition - EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task)); - EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent()); - stackView.cancelAllTaskViewAnimations(); - - if (screenPinningRequested) { - // Request screen pinning after the animation runs - mStartScreenPinningRunnable.taskId = task.key.id; - mHandler.postDelayed(mStartScreenPinningRunnable, 350); - } - - if (!Recents.getConfiguration().isLowRamDevice) { - // Reset the state where we are waiting for the transition to start - EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false)); - } - } - }; - } else { - // This is only the case if the task is not on screen (scrolled offscreen for example) - transitionFuture = null; - animStartedListener = new OnAnimationStartedListener() { - private boolean mHandled; - - @Override - public void onAnimationStarted() { - if (mHandled) { - return; - } - mHandled = true; - - // If we are launching into another task, cancel the previous task's - // window transition - EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task)); - EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent()); - stackView.cancelAllTaskViewAnimations(); - - if (!Recents.getConfiguration().isLowRamDevice) { - // Reset the state where we are waiting for the transition to start - EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false)); - } - } - }; - } - - EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true)); - final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext, - mHandler, transitionFuture != null ? transitionFuture.future : null, - animStartedListener, true /* scaleUp */); - if (taskView == null) { - // If there is no task view, then we do not need to worry about animating out occluding - // task views, and we can launch immediately - startTaskActivity(stack, task, taskView, opts, transitionFuture, destinationStack); - } else { - LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView, - screenPinningRequested); - if (task.group != null && !task.group.isFrontMostTask(task)) { - launchStartedEvent.addPostAnimationCallback(new Runnable() { - @Override - public void run() { - startTaskActivity(stack, task, taskView, opts, transitionFuture, - destinationStack); - } - }); - EventBus.getDefault().send(launchStartedEvent); - } else { - EventBus.getDefault().send(launchStartedEvent); - startTaskActivity(stack, task, taskView, opts, transitionFuture, destinationStack); - } - } - Recents.getSystemServices().sendCloseSystemWindows( - StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY); - } - - public IRemoteCallback wrapStartedListener(final OnAnimationStartedListener listener) { - if (listener == null) { - return null; - } - return new IRemoteCallback.Stub() { - @Override - public void sendResult(Bundle data) throws RemoteException { - mHandler.post(new Runnable() { - @Override - public void run() { - listener.onAnimationStarted(); - } - }); - } - }; - } - - /** - * Starts the activity for the launch task. - * - * @param taskView this is the {@link TaskView} that we are launching from. This can be null if - * we are toggling recents and the launch-to task is now offscreen. - * @param destinationStack id of the stack to put the task into. - */ - private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView, - ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture, - int destinationStack) { - SystemServicesProxy ssp = Recents.getSystemServices(); - ssp.startActivityFromRecents(mContext, task.key, task.title, opts, destinationStack, - succeeded -> { - if (succeeded) { - // Keep track of the index of the task launch - int taskIndexFromFront = 0; - int taskIndex = stack.indexOfStackTask(task); - if (taskIndex > -1) { - taskIndexFromFront = stack.getTaskCount() - taskIndex - 1; - } - EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront)); - } else { - // Dismiss the task if we fail to launch it - if (taskView != null) { - taskView.dismissTask(); - } - - // Keep track of failed launches - EventBus.getDefault().send(new LaunchTaskFailedEvent()); - } - }); - if (transitionFuture != null) { - mHandler.post(transitionFuture::precacheSpecs); - } - } - - /** - * Creates a future which will later be queried for animation specs for this current transition. - * - * @param composer The implementation that composes the specs on the UI thread. - */ - public AppTransitionAnimationSpecsFuture getAppTransitionFuture( - final AnimationSpecComposer composer) { - synchronized (this) { - mAppTransitionAnimationSpecs = SPECS_WAITING; - } - IAppTransitionAnimationSpecsFuture future = new IAppTransitionAnimationSpecsFuture.Stub() { - @Override - public AppTransitionAnimationSpec[] get() throws RemoteException { - mHandler.post(() -> { - synchronized (RecentsTransitionHelper.this) { - mAppTransitionAnimationSpecs = composer.composeSpecs(); - RecentsTransitionHelper.this.notifyAll(); - } - }); - synchronized (RecentsTransitionHelper.this) { - while (mAppTransitionAnimationSpecs == SPECS_WAITING) { - try { - RecentsTransitionHelper.this.wait(); - } catch (InterruptedException e) {} - } - if (mAppTransitionAnimationSpecs == null) { - return null; - } - AppTransitionAnimationSpec[] specs - = new AppTransitionAnimationSpec[mAppTransitionAnimationSpecs.size()]; - mAppTransitionAnimationSpecs.toArray(specs); - mAppTransitionAnimationSpecs = SPECS_WAITING; - return specs; - } - } - }; - return new AppTransitionAnimationSpecsFuture(composer, future); - } - - /** - * Composes the transition spec when docking a task, which includes a full task bitmap. - */ - public List<AppTransitionAnimationSpec> composeDockAnimationSpec(TaskView taskView, - Rect bounds) { - mTmpTransform.fillIn(taskView); - Task task = taskView.getTask(); - GraphicBuffer buffer = RecentsTransitionHelper.composeTaskBitmap(taskView, mTmpTransform); - return Collections.singletonList(new AppTransitionAnimationSpec(task.key.id, buffer, - bounds)); - } - - /** - * Composes the animation specs for all the tasks in the target stack. - */ - private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task, - final TaskStackView stackView, final int destinationStack, Rect windowRect) { - // Ensure we have a valid target stack id - final int targetStackId = destinationStack != INVALID_STACK_ID ? - destinationStack : task.key.stackId; - if (!StackId.useAnimationSpecForAppTransition(targetStackId)) { - return null; - } - - // Calculate the offscreen task rect (for tasks that are not backed by views) - TaskView taskView = stackView.getChildViewForTask(task); - TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm(); - Rect offscreenTaskRect = new Rect(); - stackLayout.getFrontOfStackTransform().rect.round(offscreenTaskRect); - - // If this is a full screen stack, the transition will be towards the single, full screen - // task. We only need the transition spec for this task. - List<AppTransitionAnimationSpec> specs = new ArrayList<>(); - - // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to - // check for INVALID_STACK_ID - if (targetStackId == FULLSCREEN_WORKSPACE_STACK_ID || targetStackId == DOCKED_STACK_ID - || targetStackId == ASSISTANT_STACK_ID || targetStackId == INVALID_STACK_ID) { - if (taskView == null) { - specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect)); - } else { - mTmpTransform.fillIn(taskView); - stackLayout.transformToScreenCoordinates(mTmpTransform, windowRect); - AppTransitionAnimationSpec spec = composeAnimationSpec(stackView, taskView, - mTmpTransform, true /* addHeaderBitmap */); - if (spec != null) { - specs.add(spec); - } - } - return specs; - } - - // Otherwise, for freeform tasks, create a new animation spec for each task we have to - // launch - TaskStack stack = stackView.getStack(); - ArrayList<Task> tasks = stack.getStackTasks(); - int taskCount = tasks.size(); - for (int i = taskCount - 1; i >= 0; i--) { - Task t = tasks.get(i); - if (t.isFreeformTask() || targetStackId == FREEFORM_WORKSPACE_STACK_ID) { - TaskView tv = stackView.getChildViewForTask(t); - if (tv == null) { - // TODO: Create a different animation task rect for this case (though it should - // never happen) - specs.add(composeOffscreenAnimationSpec(t, offscreenTaskRect)); - } else { - mTmpTransform.fillIn(taskView); - stackLayout.transformToScreenCoordinates(mTmpTransform, - null /* windowOverrideRect */); - AppTransitionAnimationSpec spec = composeAnimationSpec(stackView, tv, - mTmpTransform, true /* addHeaderBitmap */); - if (spec != null) { - specs.add(spec); - } - } - } - } - - return specs; - } - - /** - * Composes a single animation spec for the given {@link Task} - */ - private static AppTransitionAnimationSpec composeOffscreenAnimationSpec(Task task, - Rect taskRect) { - return new AppTransitionAnimationSpec(task.key.id, null, taskRect); - } - - public static GraphicBuffer composeTaskBitmap(TaskView taskView, TaskViewTransform transform) { - float scale = transform.scale; - int fromWidth = (int) (transform.rect.width() * scale); - int fromHeight = (int) (transform.rect.height() * scale); - if (fromWidth == 0 || fromHeight == 0) { - Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() + - " at transform: " + transform); - - return drawViewIntoGraphicBuffer(1, 1, null, 1f, 0x00ffffff); - } else { - if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) { - return drawViewIntoGraphicBuffer(fromWidth, fromHeight, null, 1f, 0xFFff0000); - } else { - return drawViewIntoGraphicBuffer(fromWidth, fromHeight, taskView, scale, 0); - } - } - } - - private static GraphicBuffer composeHeaderBitmap(TaskView taskView, - TaskViewTransform transform) { - float scale = transform.scale; - int headerWidth = (int) (transform.rect.width()); - int headerHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale); - if (headerWidth == 0 || headerHeight == 0) { - return null; - } - - if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) { - return drawViewIntoGraphicBuffer(headerWidth, headerHeight, null, 1f, 0xFFff0000); - } else { - return drawViewIntoGraphicBuffer(headerWidth, headerHeight, taskView.mHeaderView, - scale, 0); - } - } - - public static GraphicBuffer drawViewIntoGraphicBuffer(int bufferWidth, int bufferHeight, - View view, float scale, int eraseColor) { - RenderNode node = RenderNode.create("RecentsTransition", null); - node.setLeftTopRightBottom(0, 0, bufferWidth, bufferHeight); - node.setClipToBounds(false); - DisplayListCanvas c = node.start(bufferWidth, bufferHeight); - c.scale(scale, scale); - if (eraseColor != 0) { - c.drawColor(eraseColor); - } - if (view != null) { - view.draw(c); - } - node.end(c); - Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, bufferWidth, bufferHeight); - return hwBitmap.createGraphicBufferHandle(); - } - - /** - * Composes a single animation spec for the given {@link TaskView} - */ - private static AppTransitionAnimationSpec composeAnimationSpec(TaskStackView stackView, - TaskView taskView, TaskViewTransform transform, boolean addHeaderBitmap) { - GraphicBuffer b = null; - if (addHeaderBitmap) { - b = composeHeaderBitmap(taskView, transform); - if (b == null) { - return null; - } - } - - Rect taskRect = new Rect(); - transform.rect.round(taskRect); - // Disable in for low ram devices because each task does in Recents does not have fullscreen - // height (stackView height) and when transitioning to fullscreen app, the code below would - // force the task thumbnail to full stackView height immediately causing the transition - // jarring. - if (!Recents.getConfiguration().isLowRamDevice && taskView.getTask() != - stackView.getStack().getStackFrontMostTask(false /* includeFreeformTasks */)) { - taskRect.bottom = taskRect.top + stackView.getMeasuredHeight(); - } - return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect); - } - - public interface AnimationSpecComposer { - List<AppTransitionAnimationSpec> composeSpecs(); - } - - /** - * Class to be returned from {@link #composeAnimationSpec} that gives access to both the future - * and the anonymous class used for composing. - */ - public class AppTransitionAnimationSpecsFuture { - - private final AnimationSpecComposer composer; - private final IAppTransitionAnimationSpecsFuture future; - - private AppTransitionAnimationSpecsFuture(AnimationSpecComposer composer, - IAppTransitionAnimationSpecsFuture future) { - this.composer = composer; - this.future = future; - } - - public IAppTransitionAnimationSpecsFuture getFuture() { - return future; - } - - /** - * Manually generates and caches the spec such that they are already available when the - * future needs. - */ - public void precacheSpecs() { - synchronized (RecentsTransitionHelper.this) { - mAppTransitionAnimationSpecs = composer.composeSpecs(); - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index f38420e686ab..e3ed1aaa5506 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -16,13 +16,14 @@ package com.android.systemui.recents.views; -import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; + +import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS; -import android.animation.Animator; -import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.app.ActivityOptions.OnAnimationStartedListener; +import android.annotation.Nullable; +import android.app.ActivityOptions; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Canvas; @@ -32,10 +33,12 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.IRemoteCallback; import android.util.ArraySet; import android.util.AttributeSet; +import android.util.Log; import android.util.MathUtils; -import android.view.AppTransitionAnimationSpec; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -57,17 +60,23 @@ import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsActivity; import com.android.systemui.recents.RecentsActivityLaunchState; import com.android.systemui.recents.RecentsConfiguration; -import com.android.systemui.recents.RecentsDebugFlags; import com.android.systemui.recents.events.EventBus; +import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent; import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent; import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent; +import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent; import com.android.systemui.recents.events.activity.HideStackActionButtonEvent; import com.android.systemui.recents.events.activity.LaunchTaskEvent; +import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent; +import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent; +import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent; import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent; import com.android.systemui.recents.events.activity.ShowEmptyViewEvent; import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent; import com.android.systemui.recents.events.component.ExpandPipEvent; +import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; +import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent; import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent; import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent; @@ -78,11 +87,15 @@ import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.Utilities; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.TaskStack; -import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer; -import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat; +import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; +import com.android.systemui.shared.recents.view.RecentsTransition; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.ActivityOptionsCompat; +import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.systemui.stackdivider.WindowManagerProxy; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.phone.ScrimController; @@ -106,6 +119,7 @@ public class RecentsView extends FrameLayout { private static final int BUSY_RECENTS_TASK_COUNT = 3; + private Handler mHandler; private TaskStackView mTaskStackView; private TextView mStackActionButton; private TextView mEmptyView; @@ -114,7 +128,6 @@ public class RecentsView extends FrameLayout { private final int mStackButtonShadowColor; private boolean mAwaitingFirstLayout = true; - private boolean mLastTaskLaunchedWasFreeform; @ViewDebug.ExportedProperty(category="recents") Rect mSystemInsets = new Rect(); @@ -132,7 +145,7 @@ public class RecentsView extends FrameLayout { mMultiWindowBackgroundScrim.setAlpha(alpha); }; - private RecentsTransitionHelper mTransitionHelper; + private RecentsTransitionComposer mTransitionHelper; @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_") private RecentsViewTouchHandler mTouchHandler; private final FlingAnimationUtils mFlingAnimationUtils; @@ -154,7 +167,8 @@ public class RecentsView extends FrameLayout { setWillNotDraw(false); SystemServicesProxy ssp = Recents.getSystemServices(); - mTransitionHelper = new RecentsTransitionHelper(getContext()); + mHandler = new Handler(); + mTransitionHelper = new RecentsTransitionComposer(getContext()); mDividerSize = ssp.getDockedDividerSize(context); mTouchHandler = new RecentsViewTouchHandler(this); mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f); @@ -165,22 +179,20 @@ public class RecentsView extends FrameLayout { mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false); addView(mEmptyView); - if (RecentsDebugFlags.Static.EnableStackActionButton) { - if (mStackActionButton != null) { - removeView(mStackActionButton); - } - mStackActionButton = (TextView) inflater.inflate(Recents.getConfiguration() - .isLowRamDevice - ? R.layout.recents_low_ram_stack_action_button - : R.layout.recents_stack_action_button, - this, false); - - mStackButtonShadowRadius = mStackActionButton.getShadowRadius(); - mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(), - mStackActionButton.getShadowDy()); - mStackButtonShadowColor = mStackActionButton.getShadowColor(); - addView(mStackActionButton); + if (mStackActionButton != null) { + removeView(mStackActionButton); } + mStackActionButton = (TextView) inflater.inflate(Recents.getConfiguration() + .isLowRamDevice + ? R.layout.recents_low_ram_stack_action_button + : R.layout.recents_stack_action_button, + this, false); + + mStackButtonShadowRadius = mStackActionButton.getShadowRadius(); + mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(), + mStackActionButton.getShadowDy()); + mStackButtonShadowColor = mStackActionButton.getShadowColor(); + addView(mStackActionButton); reevaluateStyles(); } @@ -232,7 +244,6 @@ public class RecentsView extends FrameLayout { // Reset the state mAwaitingFirstLayout = !isResumingFromVisible; - mLastTaskLaunchedWasFreeform = false; // Update the stack mTaskStackView.onReload(isResumingFromVisible); @@ -288,7 +299,7 @@ public class RecentsView extends FrameLayout { * @return True if it changed. */ private boolean updateBusyness() { - final int taskCount = mTaskStackView.getStack().getStackTaskCount(); + final int taskCount = mTaskStackView.getStack().getTaskCount(); final float busyness = Math.min(taskCount, BUSY_RECENTS_TASK_COUNT) / (float) BUSY_RECENTS_TASK_COUNT; if (mBusynessFactor == busyness) { @@ -319,21 +330,13 @@ public class RecentsView extends FrameLayout { } } - /** - * Returns whether the last task launched was in the freeform stack or not. - */ - public boolean isLastTaskLaunchedFreeform() { - return mLastTaskLaunchedWasFreeform; - } - /** Launches the focused task from the first stack if possible */ public boolean launchFocusedTask(int logEvent) { if (mTaskStackView != null) { Task task = mTaskStackView.getFocusedTask(); if (task != null) { TaskView taskView = mTaskStackView.getChildViewForTask(task); - EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, - INVALID_STACK_ID, false)); + EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, false)); if (logEvent != 0) { MetricsLogger.action(getContext(), logEvent, @@ -357,32 +360,13 @@ public class RecentsView extends FrameLayout { Task task = getStack().getLaunchTarget(); if (task != null) { TaskView taskView = mTaskStackView.getChildViewForTask(task); - EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, - INVALID_STACK_ID, false)); + EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, false)); return true; } } return false; } - /** Launches a given task. */ - public boolean launchTask(Task task, Rect taskBounds, int destinationStack) { - if (mTaskStackView != null) { - // Iterate the stack views and try and find the given task. - List<TaskView> taskViews = mTaskStackView.getTaskViews(); - int taskViewCount = taskViews.size(); - for (int j = 0; j < taskViewCount; j++) { - TaskView tv = taskViews.get(j); - if (tv.getTask() == task) { - EventBus.getDefault().send(new LaunchTaskEvent(tv, task, taskBounds, - destinationStack, false)); - return true; - } - } - } - return false; - } - /** * Hides the task stack and shows the empty view. */ @@ -391,9 +375,7 @@ public class RecentsView extends FrameLayout { mEmptyView.setText(msgResId); mEmptyView.setVisibility(View.VISIBLE); mEmptyView.bringToFront(); - if (RecentsDebugFlags.Static.EnableStackActionButton) { - mStackActionButton.bringToFront(); - } + mStackActionButton.bringToFront(); } /** @@ -403,9 +385,7 @@ public class RecentsView extends FrameLayout { mEmptyView.setVisibility(View.INVISIBLE); mTaskStackView.setVisibility(View.VISIBLE); mTaskStackView.bringToFront(); - if (RecentsDebugFlags.Static.EnableStackActionButton) { - mStackActionButton.bringToFront(); - } + mStackActionButton.bringToFront(); } /** @@ -453,13 +433,11 @@ public class RecentsView extends FrameLayout { MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); } - if (RecentsDebugFlags.Static.EnableStackActionButton) { - // Measure the stack action button within the constraints of the space above the stack - Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect(); - measureChild(mStackActionButton, - MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST), - MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST)); - } + // Measure the stack action button within the constraints of the space above the stack + Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect(); + measureChild(mStackActionButton, + MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST)); setMeasuredDimension(width, height); } @@ -493,13 +471,11 @@ public class RecentsView extends FrameLayout { mBackgroundScrim.setBounds(left, top, right, bottom); mMultiWindowBackgroundScrim.setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); - if (RecentsDebugFlags.Static.EnableStackActionButton) { - // Layout the stack action button such that its drawable is start-aligned with the - // stack, vertically centered in the available space above the stack - Rect buttonBounds = getStackActionButtonBoundsFromStackLayout(); - mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right, - buttonBounds.bottom); - } + // Layout the stack action button such that its drawable is start-aligned with the + // stack, vertically centered in the available space above the stack + Rect buttonBounds = getStackActionButtonBoundsFromStackLayout(); + mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right, + buttonBounds.bottom); if (mAwaitingFirstLayout) { mAwaitingFirstLayout = false; @@ -541,7 +517,7 @@ public class RecentsView extends FrameLayout { public void onDrawForeground(Canvas canvas) { super.onDrawForeground(canvas); - ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates(); + ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates(); for (int i = visDockStates.size() - 1; i >= 0; i--) { visDockStates.get(i).viewState.draw(canvas); } @@ -549,7 +525,7 @@ public class RecentsView extends FrameLayout { @Override protected boolean verifyDrawable(Drawable who) { - ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates(); + ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates(); for (int i = visDockStates.size() - 1; i >= 0; i--) { Drawable d = visDockStates.get(i).viewState.dockAreaOverlay; if (d == who) { @@ -562,9 +538,8 @@ public class RecentsView extends FrameLayout { /**** EventBus Events ****/ public final void onBusEvent(LaunchTaskEvent event) { - mLastTaskLaunchedWasFreeform = event.task.isFreeformTask(); - mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView, - event.taskView, event.screenPinningRequested, event.targetTaskStack); + launchTaskFromRecents(getStack(), event.task, mTaskStackView, event.taskView, + event.screenPinningRequested, event.targetWindowingMode, event.targetActivityType); if (Recents.getConfiguration().isLowRamDevice) { EventBus.getDefault().send(new HideStackActionButtonEvent(false /* translate */)); } @@ -572,10 +547,8 @@ public class RecentsView extends FrameLayout { public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) { int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION; - if (RecentsDebugFlags.Static.EnableStackActionButton) { - // Hide the stack action button - EventBus.getDefault().send(new HideStackActionButtonEvent()); - } + // Hide the stack action button + EventBus.getDefault().send(new HideStackActionButtonEvent()); animateBackgroundScrim(0f, taskViewExitToHomeDuration); if (Recents.getConfiguration().isLowRamDevice) { @@ -585,8 +558,8 @@ public class RecentsView extends FrameLayout { public final void onBusEvent(DragStartEvent event) { updateVisibleDockRegions(Recents.getConfiguration().getDockStatesForCurrentOrientation(), - true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha, - TaskStack.DockState.NONE.viewState.hintTextAlpha, + true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha, + DockState.NONE.viewState.hintTextAlpha, true /* animateAlpha */, false /* animateBounds */); // Temporarily hide the stack action button without changing visibility @@ -600,15 +573,15 @@ public class RecentsView extends FrameLayout { } public final void onBusEvent(DragDropTargetChangedEvent event) { - if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) { + if (event.dropTarget == null || !(event.dropTarget instanceof DockState)) { updateVisibleDockRegions( Recents.getConfiguration().getDockStatesForCurrentOrientation(), - true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha, - TaskStack.DockState.NONE.viewState.hintTextAlpha, + true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha, + DockState.NONE.viewState.hintTextAlpha, true /* animateAlpha */, true /* animateBounds */); } else { - final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; - updateVisibleDockRegions(new TaskStack.DockState[] {dockState}, + final DockState dockState = (DockState) event.dropTarget; + updateVisibleDockRegions(new DockState[] {dockState}, false /* isDefaultDockState */, -1, -1, true /* animateAlpha */, true /* animateBounds */); } @@ -627,8 +600,8 @@ public class RecentsView extends FrameLayout { public final void onBusEvent(final DragEndEvent event) { // Handle the case where we drop onto a dock region - if (event.dropTarget instanceof TaskStack.DockState) { - final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; + if (event.dropTarget instanceof DockState) { + final DockState dockState = (DockState) event.dropTarget; // Hide the dock region updateVisibleDockRegions(null, false /* isDefaultDockState */, -1, -1, @@ -638,34 +611,27 @@ public class RecentsView extends FrameLayout { // rect to its final layout-space rect Utilities.setViewFrameFromTranslation(event.taskView); - // Dock the task and launch it - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode)) { - final OnAnimationStartedListener startedListener = - new OnAnimationStartedListener() { + final ActivityOptions options = ActivityOptionsCompat.makeSplitScreenOptions( + dockState.createMode == SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT); + if (ActivityManagerWrapper.getInstance().startActivityFromRecents(event.task.key.id, + options)) { + final Runnable animStartedListener = () -> { + EventBus.getDefault().send(new DockedFirstAnimationFrameEvent()); + // Remove the task and don't bother relaying out, as all the tasks + // will be relaid out when the stack changes on the multiwindow + // change event + getStack().removeTask(event.task, null, true /* fromDockGesture */); + }; + final Rect taskRect = getTaskRect(event.taskView); + AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture( + getHandler()) { @Override - public void onAnimationStarted() { - EventBus.getDefault().send(new DockedFirstAnimationFrameEvent()); - // Remove the task and don't bother relaying out, as all the tasks will be - // relaid out when the stack changes on the multiwindow change event - getStack().removeTask(event.task, null, true /* fromDockGesture */); + public List<AppTransitionAnimationSpecCompat> composeSpecs() { + return mTransitionHelper.composeDockAnimationSpec(event.taskView, taskRect); } }; - - final Rect taskRect = getTaskRect(event.taskView); - AppTransitionAnimationSpecsFuture future = - mTransitionHelper.getAppTransitionFuture( - new AnimationSpecComposer() { - @Override - public List<AppTransitionAnimationSpec> composeSpecs() { - return mTransitionHelper.composeDockAnimationSpec( - event.taskView, taskRect); - } - }); - ssp.overridePendingAppTransitionMultiThumbFuture(future.getFuture(), - mTransitionHelper.wrapStartedListener(startedListener), - true /* scaleUp */); - + WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture( + future, animStartedListener, getHandler(), true /* scaleUp */); MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP, event.task.getTopComponent().flattenToShortString()); } else { @@ -750,18 +716,10 @@ public class RecentsView extends FrameLayout { } public final void onBusEvent(ShowStackActionButtonEvent event) { - if (!RecentsDebugFlags.Static.EnableStackActionButton) { - return; - } - showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate); } public final void onBusEvent(HideStackActionButtonEvent event) { - if (!RecentsDebugFlags.Static.EnableStackActionButton) { - return; - } - hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */); } @@ -777,10 +735,6 @@ public class RecentsView extends FrameLayout { * Shows the stack action button. */ private void showStackActionButton(final int duration, final boolean translate) { - if (!RecentsDebugFlags.Static.EnableStackActionButton) { - return; - } - final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(); if (mStackActionButton.getVisibility() == View.INVISIBLE) { mStackActionButton.setVisibility(View.VISIBLE); @@ -813,10 +767,6 @@ public class RecentsView extends FrameLayout { * Hides the stack action button. */ private void hideStackActionButton(int duration, boolean translate) { - if (!RecentsDebugFlags.Static.EnableStackActionButton) { - return; - } - final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(); hideStackActionButton(duration, translate, postAnimationTrigger); postAnimationTrigger.flushLastDecrementRunnables(); @@ -827,10 +777,6 @@ public class RecentsView extends FrameLayout { */ private void hideStackActionButton(int duration, boolean translate, final ReferenceCountedTrigger postAnimationTrigger) { - if (!RecentsDebugFlags.Static.EnableStackActionButton) { - return; - } - if (mStackActionButton.getVisibility() == View.VISIBLE) { if (translate) { mStackActionButton.animate().translationY(mStackActionButton.getMeasuredHeight() @@ -877,15 +823,15 @@ public class RecentsView extends FrameLayout { /** * Updates the dock region to match the specified dock state. */ - private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates, + private void updateVisibleDockRegions(DockState[] newDockStates, boolean isDefaultDockState, int overrideAreaAlpha, int overrideHintAlpha, boolean animateAlpha, boolean animateBounds) { - ArraySet<TaskStack.DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates, - new ArraySet<TaskStack.DockState>()); - ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates(); + ArraySet<DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates, + new ArraySet<DockState>()); + ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates(); for (int i = visDockStates.size() - 1; i >= 0; i--) { - TaskStack.DockState dockState = visDockStates.get(i); - TaskStack.DockState.ViewState viewState = dockState.viewState; + DockState dockState = visDockStates.get(i); + DockState.ViewState viewState = dockState.viewState; if (newDockStates == null || !newDockStatesSet.contains(dockState)) { // This is no longer visible, so hide it viewState.startAnimation(null, 0, 0, TaskStackView.SLOW_SYNC_STACK_DURATION, @@ -947,7 +893,8 @@ public class RecentsView extends FrameLayout { * @return the bounds of the stack action button. */ Rect getStackActionButtonBoundsFromStackLayout() { - Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect()); + Rect actionButtonRect = new Rect( + mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect()); int left, top; if (Recents.getConfiguration().isLowRamDevice) { Rect windowRect = Recents.getSystemServices().getWindowRect(); @@ -972,6 +919,139 @@ public class RecentsView extends FrameLayout { return mStackActionButton; } + /** + * Launches the specified {@link Task}. + */ + public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task, + final TaskStackView stackView, final TaskView taskView, + final boolean screenPinningRequested, final int windowingMode, final int activityType) { + + final Runnable animStartedListener; + final AppTransitionAnimationSpecsFuture transitionFuture; + if (taskView != null) { + + // Fetch window rect here already in order not to be blocked on lock contention in WM + // when the future calls it. + final Rect windowRect = Recents.getSystemServices().getWindowRect(); + transitionFuture = new AppTransitionAnimationSpecsFuture(stackView.getHandler()) { + @Override + public List<AppTransitionAnimationSpecCompat> composeSpecs() { + return mTransitionHelper.composeAnimationSpecs(task, stackView, windowingMode, + activityType, windowRect); + } + }; + animStartedListener = new Runnable() { + private boolean mHandled; + + @Override + public void run() { + if (mHandled) { + return; + } + mHandled = true; + + // If we are launching into another task, cancel the previous task's + // window transition + EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task)); + EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent()); + stackView.cancelAllTaskViewAnimations(); + + if (screenPinningRequested) { + // Request screen pinning after the animation runs + mHandler.postDelayed(() -> { + EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext, + task.key.id)); + }, 350); + } + + if (!Recents.getConfiguration().isLowRamDevice) { + // Reset the state where we are waiting for the transition to start + EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false)); + } + } + }; + } else { + // This is only the case if the task is not on screen (scrolled offscreen for example) + transitionFuture = null; + animStartedListener = new Runnable() { + private boolean mHandled; + + @Override + public void run() { + if (mHandled) { + return; + } + mHandled = true; + + // If we are launching into another task, cancel the previous task's + // window transition + EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task)); + EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent()); + stackView.cancelAllTaskViewAnimations(); + + if (!Recents.getConfiguration().isLowRamDevice) { + // Reset the state where we are waiting for the transition to start + EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false)); + } + } + }; + } + + EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true)); + final ActivityOptions opts = RecentsTransition.createAspectScaleAnimation(mContext, + mHandler, true /* scaleUp */, transitionFuture != null ? transitionFuture : null, + animStartedListener); + if (taskView == null) { + // If there is no task view, then we do not need to worry about animating out occluding + // task views, and we can launch immediately + startTaskActivity(stack, task, taskView, opts, transitionFuture, + windowingMode, activityType); + } else { + LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView, + screenPinningRequested); + EventBus.getDefault().send(launchStartedEvent); + startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode, + activityType); + } + ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS); + } + + /** + * Starts the activity for the launch task. + * + * @param taskView this is the {@link TaskView} that we are launching from. This can be null if + * we are toggling recents and the launch-to task is now offscreen. + */ + private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView, + ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture, + int windowingMode, int activityType) { + ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(task.key, opts, + windowingMode, activityType, succeeded -> { + if (succeeded) { + // Keep track of the index of the task launch + int taskIndexFromFront = 0; + int taskIndex = stack.indexOfTask(task); + if (taskIndex > -1) { + taskIndexFromFront = stack.getTaskCount() - taskIndex - 1; + } + EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront)); + } else { + Log.e(TAG, mContext.getString(R.string.recents_launch_error_message, task.title)); + + // Dismiss the task if we fail to launch it + if (taskView != null) { + taskView.dismissTask(); + } + + // Keep track of failed launches + EventBus.getDefault().send(new LaunchTaskFailedEvent()); + } + }, getHandler()); + if (transitionFuture != null) { + mHandler.post(transitionFuture::composeSpecsSynchronous); + } + } + @Override public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { super.requestDisallowInterceptTouchEvent(disallowIntercept); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java index b6b24bcd8800..5c69ae3915b3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java @@ -17,7 +17,6 @@ package com.android.systemui.recents.views; import android.app.ActivityManager; -import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; import android.view.InputDevice; @@ -32,7 +31,6 @@ import com.android.systemui.recents.Recents; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.ConfigurationChangedEvent; import com.android.systemui.recents.events.activity.HideRecentsEvent; -import com.android.systemui.recents.events.activity.HideStackActionButtonEvent; import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent; import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent; @@ -41,8 +39,7 @@ import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.shared.recents.model.Task; import java.util.ArrayList; @@ -72,7 +69,7 @@ public class RecentsViewTouchHandler { private DropTarget mLastDropTarget; private DividerSnapAlgorithm mDividerSnapAlgorithm; private ArrayList<DropTarget> mDropTargets = new ArrayList<>(); - private ArrayList<TaskStack.DockState> mVisibleDockStates = new ArrayList<>(); + private ArrayList<DockState> mVisibleDockStates = new ArrayList<>(); public RecentsViewTouchHandler(RecentsView rv) { mRv = rv; @@ -96,7 +93,7 @@ public class RecentsViewTouchHandler { /** * Returns the set of visible dock states for this current drag. */ - public ArrayList<TaskStack.DockState> getVisibleDockStates() { + public ArrayList<DockState> getVisibleDockStates() { return mVisibleDockStates; } @@ -108,7 +105,7 @@ public class RecentsViewTouchHandler { /** Handles touch events once we have intercepted them */ public boolean onTouchEvent(MotionEvent ev) { handleTouchEvent(ev); - if (ev.getAction() == MotionEvent.ACTION_UP && mRv.getStack().getStackTaskCount() == 0) { + if (ev.getAction() == MotionEvent.ACTION_UP && mRv.getStack().getTaskCount() == 0) { EventBus.getDefault().send(new HideRecentsEvent(false, true)); } return true; @@ -148,9 +145,9 @@ public class RecentsViewTouchHandler { EventBus.getDefault().send(new ShowIncompatibleAppOverlayEvent()); } else { // Add the dock state drop targets (these take priority) - TaskStack.DockState[] dockStates = Recents.getConfiguration() + DockState[] dockStates = Recents.getConfiguration() .getDockStatesForCurrentOrientation(); - for (TaskStack.DockState dockState : dockStates) { + for (DockState dockState : dockStates) { registerDropTargetForCurrentDrag(dockState); dockState.update(mRv.getContext()); mVisibleDockStates.add(dockState); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java index 8f784b832e4c..170e39dfc360 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java @@ -30,7 +30,7 @@ import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; -import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.shared.recents.utilities.AnimationProps; /** Manages the scrims for the various system bars. */ public class SystemBarScrimViews { @@ -154,19 +154,19 @@ public class SystemBarScrimViews { public final void onBusEvent(MultiWindowStateChangedEvent event) { mHasDockedTasks = event.inMultiWindow; - animateScrimToCurrentNavBarState(event.stack.getStackTaskCount() > 0); + animateScrimToCurrentNavBarState(event.stack.getTaskCount() > 0); } public final void onBusEvent(final DragEndEvent event) { // Hide the nav bar scrims once we drop to a dock region - if (event.dropTarget instanceof TaskStack.DockState) { + if (event.dropTarget instanceof DockState) { animateScrimToCurrentNavBarState(false /* hasStackTasks */); } } public final void onBusEvent(final DragEndCancelledEvent event) { // Restore the scrims to the normal state - animateScrimToCurrentNavBarState(event.stack.getStackTaskCount() > 0); + animateScrimToCurrentNavBarState(event.stack.getTaskCount() > 0); } /** diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java index 81bf6affc94a..67d09787e9f3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java @@ -18,13 +18,11 @@ package com.android.systemui.recents.views; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.util.Log; -import android.view.View; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; @@ -37,9 +35,10 @@ import com.android.systemui.recents.RecentsDebugFlags; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent; import com.android.systemui.recents.misc.ReferenceCountedTrigger; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.TaskStack; import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm; +import com.android.systemui.shared.recents.utilities.AnimationProps; import java.util.ArrayList; import java.util.List; @@ -161,20 +160,12 @@ public class TaskStackAnimationHelper { for (int i = taskViews.size() - 1; i >= 0; i--) { TaskView tv = taskViews.get(i); Task task = tv.getTask(); - boolean currentTaskOccludesLaunchTarget = launchTargetTask != null && - launchTargetTask.group != null && - launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); - boolean hideTask = launchTargetTask != null && - launchTargetTask.isFreeformTask() && - task.isFreeformTask(); // Get the current transform for the task, which will be used to position it offscreen stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, null); - if (hideTask) { - tv.setVisibility(View.INVISIBLE); - } else if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) { + if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) { if (task.isLaunchTarget) { tv.onPrepareLaunchTargetForEnterAnimation(); } else if (isLowRamDevice && i >= taskViews.size() - @@ -195,13 +186,6 @@ public class TaskStackAnimationHelper { // com.android.server.wm.AppTransition#DEFAULT_APP_TRANSITION_DURATION} mStackView.updateTaskViewToTransform(tv, mTmpTransform, new AnimationProps(336, Interpolators.FAST_OUT_SLOW_IN)); - } else if (currentTaskOccludesLaunchTarget) { - // Move the task view slightly lower so we can animate it in - mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset); - mTmpTransform.alpha = 0f; - mStackView.updateTaskViewToTransform(tv, mTmpTransform, - AnimationProps.IMMEDIATE); - tv.setClipViewInStack(false); } } else if (launchState.launchedFromHome) { if (isLowRamDevice) { @@ -266,9 +250,6 @@ public class TaskStackAnimationHelper { int taskIndexFromBack = i; final TaskView tv = taskViews.get(i); Task task = tv.getTask(); - boolean currentTaskOccludesLaunchTarget = launchTargetTask != null && - launchTargetTask.group != null && - launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); // Get the current transform for the task, which will be updated to the final transform // to animate to depending on how recents was invoked @@ -280,21 +261,6 @@ public class TaskStackAnimationHelper { tv.onStartLaunchTargetEnterAnimation(mTmpTransform, taskViewEnterFromAppDuration, mStackView.mScreenPinningEnabled, postAnimationTrigger); - } else { - // Animate the task up if it was occluding the launch target - if (currentTaskOccludesLaunchTarget) { - AnimationProps taskAnimation = new AnimationProps( - taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN, - new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - postAnimationTrigger.decrement(); - tv.setClipViewInStack(true); - } - }); - postAnimationTrigger.increment(); - mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); - } } } else if (launchState.launchedFromHome) { @@ -423,9 +389,6 @@ public class TaskStackAnimationHelper { for (int i = 0; i < taskViewCount; i++) { TaskView tv = taskViews.get(i); Task task = tv.getTask(); - boolean currentTaskOccludesLaunchTarget = launchingTask != null && - launchingTask.group != null && - launchingTask.group.isTaskAboveTask(task, launchingTask); if (tv == launchingTaskView) { tv.setClipViewInStack(false); @@ -437,17 +400,6 @@ public class TaskStackAnimationHelper { }); tv.onStartLaunchTargetLaunchAnimation(taskViewExitToAppDuration, screenPinningRequested, postAnimationTrigger); - } else if (currentTaskOccludesLaunchTarget) { - // Animate this task out of view - AnimationProps taskAnimation = new AnimationProps( - taskViewExitToAppDuration, Interpolators.ALPHA_OUT, - postAnimationTrigger.decrementOnAnimationEnd()); - postAnimationTrigger.increment(); - - mTmpTransform.fillIn(tv); - mTmpTransform.alpha = 0f; - mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset); - mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); } } } @@ -497,7 +449,7 @@ public class TaskStackAnimationHelper { // Get the current set of task transforms int taskViewCount = mStackView.getTaskViews().size(); - ArrayList<Task> stackTasks = stack.getStackTasks(); + ArrayList<Task> stackTasks = stack.getTasks(); mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms); // Pick up the newly visible views after the scroll @@ -589,7 +541,7 @@ public class TaskStackAnimationHelper { TaskStackViewScroller stackScroller = mStackView.getScroller(); // Get the current set of task transforms - ArrayList<Task> stackTasks = newStack.getStackTasks(); + ArrayList<Task> stackTasks = newStack.getTasks(); mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms); // Update the stack @@ -611,7 +563,7 @@ public class TaskStackAnimationHelper { false /* ignoreTaskOverrides */, mTmpFinalTaskTransforms); // Hide the front most task view until the scroll is complete - Task frontMostTask = newStack.getStackFrontMostTask(false /* includeFreeform */); + Task frontMostTask = newStack.getFrontMostTask(); final TaskView frontMostTaskView = mStackView.getChildViewForTask(frontMostTask); final TaskViewTransform frontMostTransform = mTmpFinalTaskTransforms.get( stackTasks.indexOf(frontMostTask)); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index eaa32eefe795..d9f79bb6d34e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -24,7 +24,6 @@ import android.graphics.Path; import android.graphics.Rect; import android.util.ArraySet; import android.util.Log; -import android.util.MutableFloat; import android.util.SparseArray; import android.util.SparseIntArray; import android.view.ViewDebug; @@ -36,9 +35,9 @@ import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.RecentsDebugFlags; import com.android.systemui.recents.misc.FreePathInterpolator; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.Utilities; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.TaskStack; import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm; import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm; @@ -147,75 +146,6 @@ public class TaskStackLayoutAlgorithm { } /** - * The various stack/freeform states. - */ - public static class StackState { - - public static final StackState FREEFORM_ONLY = new StackState(1f, 255); - public static final StackState STACK_ONLY = new StackState(0f, 0); - public static final StackState SPLIT = new StackState(0.5f, 255); - - public final float freeformHeightPct; - public final int freeformBackgroundAlpha; - - /** - * @param freeformHeightPct the percentage of the stack height (not including paddings) to - * allocate to the freeform workspace - * @param freeformBackgroundAlpha the background alpha for the freeform workspace - */ - private StackState(float freeformHeightPct, int freeformBackgroundAlpha) { - this.freeformHeightPct = freeformHeightPct; - this.freeformBackgroundAlpha = freeformBackgroundAlpha; - } - - /** - * Resolves the stack state for the layout given a task stack. - */ - public static StackState getStackStateForStack(TaskStack stack) { - SystemServicesProxy ssp = Recents.getSystemServices(); - boolean hasFreeformWorkspaces = ssp.hasFreeformWorkspaceSupport(); - int freeformCount = stack.getFreeformTaskCount(); - int stackCount = stack.getStackTaskCount(); - if (hasFreeformWorkspaces && stackCount > 0 && freeformCount > 0) { - return SPLIT; - } else if (hasFreeformWorkspaces && freeformCount > 0) { - return FREEFORM_ONLY; - } else { - return STACK_ONLY; - } - } - - /** - * Computes the freeform and stack rect for this state. - * - * @param freeformRectOut the freeform rect to be written out - * @param stackRectOut the stack rect, we only write out the top of the stack - * @param taskStackBounds the full rect that the freeform rect can take up - */ - public void computeRects(Rect freeformRectOut, Rect stackRectOut, - Rect taskStackBounds, int topMargin, int freeformGap, int stackBottomOffset) { - // The freeform height is the visible height (not including system insets) - padding - // above freeform and below stack - gap between the freeform and stack - int availableHeight = taskStackBounds.height() - topMargin - stackBottomOffset; - int ffPaddedHeight = (int) (availableHeight * freeformHeightPct); - int ffHeight = Math.max(0, ffPaddedHeight - freeformGap); - freeformRectOut.set(taskStackBounds.left, - taskStackBounds.top + topMargin, - taskStackBounds.right, - taskStackBounds.top + topMargin + ffHeight); - stackRectOut.set(taskStackBounds.left, - taskStackBounds.top, - taskStackBounds.right, - taskStackBounds.bottom); - if (ffPaddedHeight > 0) { - stackRectOut.top += ffPaddedHeight; - } else { - stackRectOut.top += topMargin; - } - } - } - - /** * @return True if we should use the grid layout. */ boolean useGridLayout() { @@ -234,15 +164,11 @@ public class TaskStackLayoutAlgorithm { } Context mContext; - private StackState mState = StackState.SPLIT; private TaskStackLayoutAlgorithmCallbacks mCb; // The task bounds (untransformed) for layout. This rect is anchored at mTaskRoot. @ViewDebug.ExportedProperty(category="recents") public Rect mTaskRect = new Rect(); - // The freeform workspace bounds, inset by the top system insets and is a fixed height - @ViewDebug.ExportedProperty(category="recents") - public Rect mFreeformRect = new Rect(); // The stack bounds, inset from the top system insets, and runs to the bottom of the screen @ViewDebug.ExportedProperty(category="recents") public Rect mStackRect = new Rect(); @@ -268,10 +194,6 @@ public class TaskStackLayoutAlgorithm { private int mBaseBottomMargin; private int mMinMargin; - // The gap between the freeform and stack layouts - @ViewDebug.ExportedProperty(category="recents") - private int mFreeformStackGap; - // The initial offset that the focused task is from the top @ViewDebug.ExportedProperty(category="recents") private int mInitialTopOffset; @@ -331,8 +253,6 @@ public class TaskStackLayoutAlgorithm { // The last computed task counts @ViewDebug.ExportedProperty(category="recents") int mNumStackTasks; - @ViewDebug.ExportedProperty(category="recents") - int mNumFreeformTasks; // The min/max z translations @ViewDebug.ExportedProperty(category="recents") @@ -344,8 +264,6 @@ public class TaskStackLayoutAlgorithm { private SparseIntArray mTaskIndexMap = new SparseIntArray(); private SparseArray<Float> mTaskIndexOverrideMap = new SparseArray<>(); - // The freeform workspace layout - FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm; TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm; TaskStackLowRamLayoutAlgorithm mTaskStackLowRamLayoutAlgorithm; @@ -356,7 +274,6 @@ public class TaskStackLayoutAlgorithm { public TaskStackLayoutAlgorithm(Context context, TaskStackLayoutAlgorithmCallbacks cb) { mContext = context; mCb = cb; - mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context); mTaskGridLayoutAlgorithm = new TaskGridLayoutAlgorithm(context); mTaskStackLowRamLayoutAlgorithm = new TaskStackLowRamLayoutAlgorithm(context); reloadOnConfigurationChange(context); @@ -393,7 +310,6 @@ public class TaskStackLayoutAlgorithm { R.dimen.recents_layout_initial_bottom_offset_tablet, R.dimen.recents_layout_initial_bottom_offset_tablet, R.dimen.recents_layout_initial_bottom_offset_tablet); - mFreeformLayoutAlgorithm.reloadOnConfigurationChange(context); mTaskGridLayoutAlgorithm.reloadOnConfigurationChange(context); mTaskStackLowRamLayoutAlgorithm.reloadOnConfigurationChange(context); mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin); @@ -408,8 +324,6 @@ public class TaskStackLayoutAlgorithm { R.dimen.recents_layout_side_margin_tablet_xlarge, R.dimen.recents_layout_side_margin_tablet); mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin); - mFreeformStackGap = - res.getDimensionPixelSize(R.dimen.recents_freeform_layout_bottom_margin); mTitleBarHeight = getDimensionForDevice(mContext, R.dimen.recents_task_view_header_height, R.dimen.recents_task_view_header_height, @@ -462,8 +376,7 @@ public class TaskStackLayoutAlgorithm { * Computes the stack and task rects. The given task stack bounds already has the top/right * insets and left/right padding already applied. */ - public void initialize(Rect displayRect, Rect windowRect, Rect taskStackBounds, - StackState state) { + public void initialize(Rect displayRect, Rect windowRect, Rect taskStackBounds) { Rect lastStackRect = new Rect(mStackRect); int topMargin = getScaleForExtent(windowRect, displayRect, mBaseTopMargin, mMinMargin, HEIGHT); @@ -474,10 +387,9 @@ public class TaskStackLayoutAlgorithm { mInitialBottomOffset = mBaseInitialBottomOffset; // Compute the stack bounds - mState = state; mStackBottomOffset = mSystemInsets.bottom + bottomMargin; - state.computeRects(mFreeformRect, mStackRect, taskStackBounds, topMargin, - mFreeformStackGap, mStackBottomOffset); + mStackRect.set(taskStackBounds); + mStackRect.top += topMargin; // The stack action button will take the full un-padded header space above the stack mStackActionButtonRect.set(mStackRect.left, mStackRect.top - topMargin, @@ -519,37 +431,31 @@ public class TaskStackLayoutAlgorithm { * in the stack. */ public void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet, - RecentsActivityLaunchState launchState) { + RecentsActivityLaunchState launchState, float lastScrollPPercent) { SystemServicesProxy ssp = Recents.getSystemServices(); // Clear the progress map mTaskIndexMap.clear(); // Return early if we have no tasks - ArrayList<Task> tasks = stack.getStackTasks(); + ArrayList<Task> tasks = stack.getTasks(); if (tasks.isEmpty()) { mFrontMostTaskP = 0; mMinScrollP = mMaxScrollP = mInitialScrollP = 0; - mNumStackTasks = mNumFreeformTasks = 0; + mNumStackTasks = 0; return; } - // Filter the set of freeform and stack tasks - ArrayList<Task> freeformTasks = new ArrayList<>(); + // Filter the set of stack tasks ArrayList<Task> stackTasks = new ArrayList<>(); for (int i = 0; i < tasks.size(); i++) { Task task = tasks.get(i); if (ignoreTasksSet.contains(task.key)) { continue; } - if (task.isFreeformTask()) { - freeformTasks.add(task); - } else { - stackTasks.add(task); - } + stackTasks.add(task); } mNumStackTasks = stackTasks.size(); - mNumFreeformTasks = freeformTasks.size(); // Put each of the tasks in the progress map at a fixed index (does not need to actually // map to a scroll position, just by index) @@ -559,15 +465,10 @@ public class TaskStackLayoutAlgorithm { mTaskIndexMap.put(task.key.id, i); } - // Update the freeform tasks - if (!freeformTasks.isEmpty()) { - mFreeformLayoutAlgorithm.update(freeformTasks, this); - } - // Calculate the min/max/initial scroll Task launchTask = stack.getLaunchTarget(); int launchTaskIndex = launchTask != null - ? stack.indexOfStackTask(launchTask) + ? stack.indexOfTask(launchTask) : mNumStackTasks - 1; if (getInitialFocusState() == STATE_FOCUSED) { int maxBottomOffset = mStackBottomOffset + mTaskRect.height(); @@ -582,7 +483,7 @@ public class TaskStackLayoutAlgorithm { } else { mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, mMaxScrollP); } - } else if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) { + } else if (mNumStackTasks == 1) { // If there is one stack task, ignore the min/max/initial scroll positions mMinScrollP = 0; mMaxScrollP = 0; @@ -603,10 +504,10 @@ public class TaskStackLayoutAlgorithm { boolean scrollToFront = launchState.launchedFromHome || launchState.launchedFromPipApp || launchState.launchedWithNextPipApp || launchState.launchedViaDockGesture; - if (launchState.launchedFromBlacklistedApp) { - mInitialScrollP = mMaxScrollP; - } else if (launchState.launchedWithAltTab) { + if (launchState.launchedWithAltTab) { mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP); + } else if (0 <= lastScrollPPercent && lastScrollPPercent <= 1) { + mInitialScrollP = Utilities.mapRange(lastScrollPPercent, mMinScrollP, mMaxScrollP); } else if (Recents.getConfiguration().isLowRamDevice) { mInitialScrollP = mTaskStackLowRamLayoutAlgorithm.getInitialScrollP(mNumStackTasks, scrollToFront); @@ -633,7 +534,6 @@ public class TaskStackLayoutAlgorithm { boolean scrollToFront = launchState.launchedFromHome || launchState.launchedFromPipApp || launchState.launchedWithNextPipApp || - launchState.launchedFromBlacklistedApp || launchState.launchedViaDockGesture; if (getInitialFocusState() == STATE_UNFOCUSED && mNumStackTasks > 1) { if (ignoreScrollToFront || (!launchState.launchedWithAltTab && !scrollToFront)) { @@ -659,7 +559,7 @@ public class TaskStackLayoutAlgorithm { } mUnfocusedRange.offset(0f); - List<Task> tasks = stack.getStackTasks(); + List<Task> tasks = stack.getTasks(); int taskCount = tasks.size(); for (int i = taskCount - 1; i >= 0; i--) { int indexFromFront = taskCount - i - 1; @@ -767,7 +667,7 @@ public class TaskStackLayoutAlgorithm { public int getInitialFocusState() { RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); RecentsDebugFlags debugFlags = Recents.getDebugFlags(); - if (debugFlags.isPagingEnabled() || launchState.launchedWithAltTab) { + if (launchState.launchedWithAltTab) { return STATE_FOCUSED; } else { return STATE_UNFOCUSED; @@ -794,13 +694,6 @@ public class TaskStackLayoutAlgorithm { } /** - * Returns the current stack state. - */ - public StackState getStackState() { - return mState; - } - - /** * Returns whether this stack layout has been initialized. */ public boolean isInitialized() { @@ -825,62 +718,44 @@ public class TaskStackLayoutAlgorithm { return new VisibilityReport(1, 1); } - // Quick return when there are no stack tasks - if (mNumStackTasks == 0) { - return new VisibilityReport(mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0, - mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0); - } - // Otherwise, walk backwards in the stack and count the number of tasks and visible - // thumbnails and add that to the total freeform task count + // thumbnails and add that to the total task count TaskViewTransform tmpTransform = new TaskViewTransform(); Range currentRange = getInitialFocusState() > 0f ? mFocusedRange : mUnfocusedRange; currentRange.offset(mInitialScrollP); int taskBarHeight = mContext.getResources().getDimensionPixelSize( R.dimen.recents_task_view_header_height); - int numVisibleTasks = mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0; - int numVisibleThumbnails = mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 0) : 0; + int numVisibleTasks = 0; + int numVisibleThumbnails = 0; float prevScreenY = Integer.MAX_VALUE; for (int i = tasks.size() - 1; i >= 0; i--) { Task task = tasks.get(i); - // Skip freeform - if (task.isFreeformTask()) { - continue; - } - // Skip invisible float taskProgress = getStackScrollForTask(task); if (!currentRange.isInRange(taskProgress)) { continue; } - boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task); - if (isFrontMostTaskInGroup) { - getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState, - tmpTransform, null, false /* ignoreSingleTaskCase */, - false /* forceUpdate */); - float screenY = tmpTransform.rect.top; - boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight; - if (hasVisibleThumbnail) { - numVisibleThumbnails++; - numVisibleTasks++; - prevScreenY = screenY; - } else { - // Once we hit the next front most task that does not have a visible thumbnail, - // walk through remaining visible set - for (int j = i; j >= 0; j--) { - taskProgress = getStackScrollForTask(tasks.get(j)); - if (!currentRange.isInRange(taskProgress)) { - break; - } - numVisibleTasks++; + getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState, + tmpTransform, null, false /* ignoreSingleTaskCase */, false /* forceUpdate */); + float screenY = tmpTransform.rect.top; + boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight; + if (hasVisibleThumbnail) { + numVisibleThumbnails++; + numVisibleTasks++; + prevScreenY = screenY; + } else { + // Once we hit the next front most task that does not have a visible thumbnail, + // walk through remaining visible set + for (int j = i; j >= 0; j--) { + taskProgress = getStackScrollForTask(tasks.get(j)); + if (!currentRange.isInRange(taskProgress)) { + break; } - break; + numVisibleTasks++; } - } else { - // Affiliated task, no thumbnail - numVisibleTasks++; + break; } } return new VisibilityReport(numVisibleTasks, numVisibleThumbnails); @@ -906,10 +781,7 @@ public class TaskStackLayoutAlgorithm { public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState, TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate, boolean ignoreTaskOverrides) { - if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) { - mFreeformLayoutAlgorithm.getTransform(task, transformOut, this); - return transformOut; - } else if (useGridLayout()) { + if (useGridLayout()) { int taskIndex = mTaskIndexMap.get(task.key.id); int taskCount = mTaskIndexMap.size(); mTaskGridLayoutAlgorithm.getTransform(taskIndex, taskCount, transformOut, this); @@ -1024,7 +896,7 @@ public class TaskStackLayoutAlgorithm { float z; float dimAlpha; float viewOutlineAlpha; - if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) { + if (mNumStackTasks == 1 && !ignoreSingleTaskCase) { // When there is exactly one task, then decouple the task from the stack and just move // in screen space float tmpP = (mMinScrollP - stackScroll) / mNumStackTasks; @@ -1378,7 +1250,6 @@ public class TaskStackLayoutAlgorithm { writer.print("insets="); writer.print(Utilities.dumpRect(mSystemInsets)); writer.print(" stack="); writer.print(Utilities.dumpRect(mStackRect)); writer.print(" task="); writer.print(Utilities.dumpRect(mTaskRect)); - writer.print(" freeform="); writer.print(Utilities.dumpRect(mFreeformRect)); writer.print(" actionButton="); writer.print(Utilities.dumpRect(mStackActionButtonRect)); writer.println(); 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 d32b2208697a..3cc3273c0db4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -16,23 +16,15 @@ package com.android.systemui.recents.views; -import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; -import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; -import static android.app.ActivityManager.StackId.INVALID_STACK_ID; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.IntDef; import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Canvas; import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; import android.os.Bundle; import android.provider.Settings; import android.util.ArrayMap; @@ -65,7 +57,6 @@ import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimatio import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent; import com.android.systemui.recents.events.activity.HideRecentsEvent; import com.android.systemui.recents.events.activity.HideStackActionButtonEvent; -import com.android.systemui.recents.events.activity.IterateRecentsEvent; import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent; import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent; import com.android.systemui.recents.events.activity.LaunchTaskEvent; @@ -84,13 +75,11 @@ import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; import com.android.systemui.recents.events.ui.DismissTaskViewEvent; import com.android.systemui.recents.events.ui.RecentsGrowingEvent; import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; -import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent; import com.android.systemui.recents.events.ui.UserInteractionEvent; import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; -import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent; import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent; import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent; import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent; @@ -98,13 +87,15 @@ import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent; import com.android.systemui.recents.misc.DozeTrigger; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.Utilities; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.shared.recents.utilities.AnimationProps; +import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.TaskStack; import com.android.systemui.recents.views.grid.GridTaskView; import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm; import com.android.systemui.recents.views.grid.TaskViewFocusFrame; +import com.android.systemui.shared.system.ActivityManagerWrapper; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -154,8 +145,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_") private TaskStackViewTouchHandler mTouchHandler; private TaskStackAnimationHelper mAnimationHelper; - private GradientDrawable mFreeformWorkspaceBackground; - private ObjectAnimator mFreeformWorkspaceBackgroundAnimator; private ViewPool<TaskView, Task> mViewPool; private ArrayList<TaskView> mTaskViews = new ArrayList<>(); @@ -221,6 +210,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal private int mLastHeight; private boolean mStackActionButtonVisible; + // Percentage of last ScrollP from the min to max scrollP that lives after configuration changes + private float mLastScrollPPercent = -1; + // We keep track of the task view focused by user interaction and draw a frame around it in the // grid layout. private TaskViewFocusFrame mTaskViewFocusFrame; @@ -240,20 +232,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } }; - // The drop targets for a task drag - private DropTarget mFreeformWorkspaceDropTarget = new DropTarget() { - @Override - public boolean acceptsDrop(int x, int y, int width, int height, Rect insets, - boolean isCurrentTarget) { - // This drop target has a fixed bounds and should be checked last, so just fall through - // if it is the current target - if (!isCurrentTarget) { - return mLayoutAlgorithm.mFreeformRect.contains(x, y); - } - return false; - } - }; - private DropTarget mStackDropTarget = new DropTarget() { @Override public boolean acceptsDrop(int x, int y, int width, int height, Rect insets, @@ -313,17 +291,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } }); setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - if (ssp.hasFreeformWorkspaceSupport()) { - setWillNotDraw(false); - } - - mFreeformWorkspaceBackground = (GradientDrawable) getContext().getDrawable( - R.drawable.recents_freeform_workspace_bg); - mFreeformWorkspaceBackground.setCallback(this); - if (ssp.hasFreeformWorkspaceSupport()) { - mFreeformWorkspaceBackground.setColor( - getContext().getColor(R.color.recents_freeform_workspace_bg_color)); - } } @Override @@ -360,15 +327,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal readSystemFlags(); mTaskViewsClipDirty = true; mUIDozeTrigger.stopDozing(); - if (isResumingFromVisible) { - // Animate in the freeform workspace - int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha; - animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150, - Interpolators.FAST_OUT_SLOW_IN)); - } else { + if (!isResumingFromVisible) { mStackScroller.reset(); mStableLayoutAlgorithm.reset(); mLayoutAlgorithm.reset(); + mLastScrollPPercent = -1; } // Since we always animate to the same place in (the initial state), always reset the stack @@ -388,7 +351,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Only notify if we are already initialized, otherwise, everything will pick up all the // new and old tasks when we next layout - mStack.setTasks(getContext(), stack, allowNotifyStackChanges && isInitialized); + mStack.setTasks(stack, allowNotifyStackChanges && isInitialized); } /** Returns the task stack. */ @@ -423,23 +386,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal /** * Returns the front most task view. - * - * @param stackTasksOnly if set, will return the front most task view in the stack (by default - * the front most task view will be freeform since they are placed above - * stack tasks) */ - private TaskView getFrontMostTaskView(boolean stackTasksOnly) { + private TaskView getFrontMostTaskView() { List<TaskView> taskViews = getTaskViews(); - int taskViewCount = taskViews.size(); - for (int i = taskViewCount - 1; i >= 0; i--) { - TaskView tv = taskViews.get(i); - Task task = tv.getTask(); - if (stackTasksOnly && task.isFreeformTask()) { - continue; - } - return tv; + if (taskViews.isEmpty()) { + return null; } - return null; + return taskViews.get(taskViews.size() - 1); } /** @@ -501,8 +454,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal * visible range includes all tasks at the target stack scroll. This is useful for ensure that * all views necessary for a transition or animation will be visible at the start. * - * This call ignores freeform tasks. - * * @param taskTransforms The set of task view transforms to reuse, this list will be sized to * match the size of {@param tasks} * @param tasks The set of tasks for which to generate transforms @@ -525,7 +476,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0; // We can reuse the task transforms where possible to reduce object allocation - Utilities.matchTaskListSize(tasks, taskTransforms); + matchTaskListSize(tasks, taskTransforms); // Update the stack transforms TaskViewTransform frontTransform = null; @@ -555,12 +506,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal continue; } - // For freeform tasks, only calculate the stack transform and skip the calculation of - // the visible stack indices - if (task.isFreeformTask()) { - continue; - } - frontTransform = transform; frontTransformAtTarget = transformAtTarget; if (transform.visible) { @@ -595,7 +540,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal */ void bindVisibleTaskViews(float targetStackScroll, boolean ignoreTaskOverrides) { // Get all the task transforms - ArrayList<Task> tasks = mStack.getStackTasks(); + ArrayList<Task> tasks = mStack.getTasks(); int[] visibleTaskRange = computeVisibleTaskTransforms(mCurrentTaskTransforms, tasks, mStackScroller.getStackScroll(), targetStackScroll, mIgnoreTasks, ignoreTaskOverrides); @@ -617,13 +562,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // It is possible for the set of lingering TaskViews to differ from the stack if the // stack was updated before the relayout. If the task view is no longer in the stack, // then just return it back to the view pool. - int taskIndex = mStack.indexOfStackTask(task); + int taskIndex = mStack.indexOfTask(task); TaskViewTransform transform = null; if (taskIndex != -1) { transform = mCurrentTaskTransforms.get(taskIndex); } - if (task.isFreeformTask() || (transform != null && transform.visible)) { + if (transform != null && transform.visible) { mTmpTaskViewMap.put(task.key, tv); } else { if (mTouchExplorationEnabled && Utilities.isDescendentAccessibilityFocused(tv)) { @@ -644,28 +589,24 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal continue; } - // Skip the invisible non-freeform stack tasks - if (!task.isFreeformTask() && !transform.visible) { + // Skip the invisible stack tasks + if (!transform.visible) { continue; } TaskView tv = mTmpTaskViewMap.get(task.key); if (tv == null) { tv = mViewPool.pickUpViewFromPool(task, task); - if (task.isFreeformTask()) { - updateTaskViewToTransform(tv, transform, AnimationProps.IMMEDIATE); + if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) { + updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(), + AnimationProps.IMMEDIATE); } else { - if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) { - updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(), - AnimationProps.IMMEDIATE); - } else { - updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(), - AnimationProps.IMMEDIATE); - } + updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(), + AnimationProps.IMMEDIATE); } } else { // Reattach it in the right z order - final int taskIndex = mStack.indexOfStackTask(task); + final int taskIndex = mStack.indexOfTask(task); final int insertIndex = findTaskViewInsertIndex(task, taskIndex); if (insertIndex != getTaskViews().indexOf(tv)){ detachViewFromParent(tv); @@ -707,14 +648,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal * an animation provided in {@param animationOverrides}, that will be used instead. */ private void relayoutTaskViews(AnimationProps animation, - ArrayMap<Task, AnimationProps> animationOverrides, - boolean ignoreTaskOverrides) { + ArrayMap<Task, AnimationProps> animationOverrides, boolean ignoreTaskOverrides) { // If we had a deferred animation, cancel that cancelDeferredTaskViewLayoutAnimation(); // Synchronize the current set of TaskViews - bindVisibleTaskViews(mStackScroller.getStackScroll(), - ignoreTaskOverrides /* ignoreTaskOverrides */); + bindVisibleTaskViews(mStackScroller.getStackScroll(), ignoreTaskOverrides); // Animate them to their final transforms with the given animation List<TaskView> taskViews = getTaskViews(); @@ -727,7 +666,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal continue; } - int taskIndex = mStack.indexOfStackTask(task); + int taskIndex = mStack.indexOfTask(task); TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex); if (animationOverrides != null && animationOverrides.containsKey(task)) { animation = animationOverrides.get(task); @@ -765,7 +704,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal */ public void getCurrentTaskTransforms(ArrayList<Task> tasks, ArrayList<TaskViewTransform> transformsOut) { - Utilities.matchTaskListSize(tasks, transformsOut); + matchTaskListSize(tasks, transformsOut); int focusState = mLayoutAlgorithm.getFocusState(); for (int i = tasks.size() - 1; i >= 0; i--) { Task task = tasks.get(i); @@ -788,7 +727,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal */ public void getLayoutTaskTransforms(float stackScroll, int focusState, ArrayList<Task> tasks, boolean ignoreTaskOverrides, ArrayList<TaskViewTransform> transformsOut) { - Utilities.matchTaskListSize(tasks, transformsOut); + matchTaskListSize(tasks, transformsOut); for (int i = tasks.size() - 1; i >= 0; i--) { Task task = tasks.get(i); TaskViewTransform transform = transformsOut.get(i); @@ -886,14 +825,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax, RecentsActivityLaunchState launchState) { // Compute the min and max scroll values - mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState); - - // Update the freeform workspace background - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.hasFreeformWorkspaceSupport()) { - mTmpRect.set(mLayoutAlgorithm.mFreeformRect); - mFreeformWorkspaceBackground.setBounds(mTmpRect); - } + mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState, mLastScrollPPercent); if (boundScrollToNewMinMax) { mStackScroller.boundScroll(); @@ -907,8 +839,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mWindowRect.set(mStableWindowRect); mStackBounds.set(mStableStackBounds); mLayoutAlgorithm.setSystemInsets(mStableLayoutAlgorithm.mSystemInsets); - mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds, - TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); + mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds); updateLayoutAlgorithm(true /* boundScroll */); } @@ -938,7 +869,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int newFocusedTaskIndex = mStack.getTaskCount() > 0 ? Utilities.clamp(focusTaskIndex, 0, mStack.getTaskCount() - 1) : -1; final Task newFocusedTask = (newFocusedTaskIndex != -1) ? - mStack.getStackTasks().get(newFocusedTaskIndex) : null; + mStack.getTasks().get(newFocusedTaskIndex) : null; // Reset the last focused task state if changed if (mFocusedTask != null) { @@ -1025,25 +956,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated, boolean cancelWindowAnimations, int timerIndicatorDuration) { Task focusedTask = getFocusedTask(); - int newIndex = mStack.indexOfStackTask(focusedTask); + int newIndex = mStack.indexOfTask(focusedTask); if (focusedTask != null) { if (stackTasksOnly) { - List<Task> tasks = mStack.getStackTasks(); - if (focusedTask.isFreeformTask()) { - // Try and focus the front most stack task - TaskView tv = getFrontMostTaskView(stackTasksOnly); - if (tv != null) { - newIndex = mStack.indexOfStackTask(tv.getTask()); - } - } else { - // Try the next task if it is a stack task - int tmpNewIndex = newIndex + (forward ? -1 : 1); - if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) { - Task t = tasks.get(tmpNewIndex); - if (!t.isFreeformTask()) { - newIndex = tmpNewIndex; - } - } + List<Task> tasks = mStack.getTasks(); + // Try the next task if it is a stack task + int tmpNewIndex = newIndex + (forward ? -1 : 1); + if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) { + newIndex = tmpNewIndex; } } else { // No restrictions, lets just move to the new task (looping forward/backwards if @@ -1054,7 +974,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } else { // We don't have a focused task float stackScroll = mStackScroller.getStackScroll(); - ArrayList<Task> tasks = mStack.getStackTasks(); + ArrayList<Task> tasks = mStack.getTasks(); int taskCount = tasks.size(); if (useGridLayout()) { // For the grid layout, we directly set focus to the most recently used task @@ -1128,7 +1048,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal return tv.getTask(); } } - TaskView frontTv = getFrontMostTaskView(true /* stackTasksOnly */); + TaskView frontTv = getFrontMostTaskView(); if (frontTv != null) { return frontTv.getTask(); } @@ -1143,8 +1063,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (taskViewCount > 0) { TaskView backMostTask = taskViews.get(0); TaskView frontMostTask = taskViews.get(taskViewCount - 1); - event.setFromIndex(mStack.indexOfStackTask(backMostTask.getTask())); - event.setToIndex(mStack.indexOfStackTask(frontMostTask.getTask())); + event.setFromIndex(mStack.indexOfTask(backMostTask.getTask())); + event.setToIndex(mStack.indexOfTask(frontMostTask.getTask())); event.setContentDescription(frontMostTask.getTask().title); } event.setItemCount(mStack.getTaskCount()); @@ -1163,7 +1083,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Find the accessibility focused task Task focusedTask = getAccessibilityFocusedTask(); info.setScrollable(true); - int focusedTaskIndex = mStack.indexOfStackTask(focusedTask); + int focusedTaskIndex = mStack.indexOfTask(focusedTask); if (focusedTaskIndex > 0 || !mStackActionButtonVisible) { info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); } @@ -1184,7 +1104,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal return true; } Task focusedTask = getAccessibilityFocusedTask(); - int taskIndex = mStack.indexOfStackTask(focusedTask); + int taskIndex = mStack.indexOfTask(focusedTask); if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) { switch (action) { case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { @@ -1233,6 +1153,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (mTaskViewsClipDirty) { clipTaskViews(); } + mLastScrollPPercent = Utilities.clamp(Utilities.unmapRange(mStackScroller.getStackScroll(), + mLayoutAlgorithm.mMinScrollP, mLayoutAlgorithm.mMaxScrollP), 0, 1); } /** @@ -1240,7 +1162,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal * updateLayoutForStack() is called first. */ public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() { - return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getStackTasks()); + return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks()); } /** @@ -1279,10 +1201,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // Compute the rects in the stack algorithm - mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds, - TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); - mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds, - TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); + mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds); + mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds); updateLayoutAlgorithm(false /* boundScroll */); // If this is the first layout, then scroll to the front of the stack, then update the @@ -1405,11 +1325,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Setup the view for the enter animation mAnimationHelper.prepareForEnterAnimation(); - // Animate in the freeform workspace - int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha; - animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150, - Interpolators.FAST_OUT_SLOW_IN)); - // Set the task focused state without requesting view focus, and leave the focus animations // until after the enter-animation RecentsConfiguration config = Recents.getConfiguration(); @@ -1418,10 +1333,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // We set the initial focused task view iff the following conditions are satisfied: // 1. Recents is showing task views in stack layout. // 2. Recents is launched with ALT + TAB. - boolean setFocusOnFirstLayout = !useGridLayout() || - Recents.getConfiguration().getLaunchState().launchedWithAltTab; + boolean setFocusOnFirstLayout = !useGridLayout() || launchState.launchedWithAltTab; if (setFocusOnFirstLayout) { - int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount(), + int focusedTaskIndex = getInitialFocusTaskIndex(launchState, mStack.getTaskCount(), useGridLayout()); if (focusedTaskIndex != -1) { setFocusedTask(focusedTaskIndex, false /* scrollToTask */, @@ -1457,43 +1371,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal return null; } - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - // Draw the freeform workspace background - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.hasFreeformWorkspaceSupport()) { - if (mFreeformWorkspaceBackground.getAlpha() > 0) { - mFreeformWorkspaceBackground.draw(canvas); - } - } - } - - @Override - protected boolean verifyDrawable(Drawable who) { - if (who == mFreeformWorkspaceBackground) { - return true; - } - return super.verifyDrawable(who); - } - - /** - * Launches the freeform tasks. - */ - public boolean launchFreeformTasks() { - ArrayList<Task> tasks = mStack.getFreeformTasks(); - if (!tasks.isEmpty()) { - Task frontTask = tasks.get(tasks.size() - 1); - if (frontTask != null && frontTask.isFreeformTask()) { - EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(frontTask), - frontTask, null, INVALID_STACK_ID, false)); - return true; - } - } - return false; - } - /**** TaskStackCallbacks Implementation ****/ @Override @@ -1629,7 +1506,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override public void onPickUpViewFromPool(TaskView tv, Task task, boolean isNewView) { // Find the index where this task should be placed in the stack - int taskIndex = mStack.indexOfStackTask(task); + int taskIndex = mStack.indexOfTask(task); int insertIndex = findTaskViewInsertIndex(task, taskIndex); // Add/attach the view to the hierarchy @@ -1672,8 +1549,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // Restore the action button visibility if it is the front most task view - if (mScreenPinningEnabled && tv.getTask() == - mStack.getStackFrontMostTask(false /* includeFreeform */)) { + if (mScreenPinningEnabled && tv.getTask() == mStack.getFrontMostTask()) { tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */); } } @@ -1689,7 +1565,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // If the doze trigger has already fired, then update the state for this task view if (mUIDozeTrigger.isAsleep() || - Recents.getSystemServices().hasFreeformWorkspaceSupport() || useGridLayout() || Recents.getConfiguration().isLowRamDevice) { tv.setNoUserInteractionState(); } @@ -1798,7 +1673,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal event.packageName, event.userId); // For other tasks, just remove them directly if they no longer exist - ArrayList<Task> tasks = mStack.getStackTasks(); + ArrayList<Task> tasks = mStack.getTasks(); for (int i = tasks.size() - 1; i >= 0; i--) { final Task t = tasks.get(i); if (removedComponents.contains(t.key.getComponent())) { @@ -1821,21 +1696,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public final void onBusEvent(LaunchMostRecentTaskRequestEvent event) { if (mStack.getTaskCount() > 0) { - Task mostRecentTask = mStack.getStackFrontMostTask(true /* includeFreefromTasks */); + Task mostRecentTask = mStack.getFrontMostTask(); launchTask(mostRecentTask); } } public final void onBusEvent(ShowStackActionButtonEvent event) { - if (RecentsDebugFlags.Static.EnableStackActionButton) { - mStackActionButtonVisible = true; - } + mStackActionButtonVisible = true; } public final void onBusEvent(HideStackActionButtonEvent event) { - if (RecentsDebugFlags.Static.EnableStackActionButton) { - mStackActionButtonVisible = false; - } + mStackActionButtonVisible = false; } public final void onBusEvent(LaunchNextTaskRequestEvent event) { @@ -1892,11 +1763,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Start the task animations mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger()); - // Dismiss the freeform workspace background - int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION; - animateFreeformWorkspaceBackgroundAlpha(0, new AnimationProps(taskViewExitToHomeDuration, - Interpolators.FAST_OUT_SLOW_IN)); - // Dismiss the grid task view focus frame if (mTaskViewFocusFrame != null) { mTaskViewFocusFrame.moveGridTaskViewFocus(null); @@ -1924,7 +1790,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public final void onBusEvent(final DismissAllTaskViewsEvent event) { // Keep track of the tasks which will have their data removed - ArrayList<Task> tasks = new ArrayList<>(mStack.getStackTasks()); + ArrayList<Task> tasks = new ArrayList<>(mStack.getTasks()); mAnimationHelper.startDeleteAllTasksAnimation( getTaskViews(), useGridLayout(), event.getAnimationTrigger()); event.addPostAnimationCallback(new Runnable() { @@ -1978,8 +1844,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mStackScroller.stopScroller(); mStackScroller.stopBoundScrollAnimation(); - setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false, - event.timerIndicatorDuration); + setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false, 0); } public final void onBusEvent(FocusPreviousTaskViewEvent event) { @@ -1993,7 +1858,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public final void onBusEvent(NavigateTaskViewEvent event) { if (useGridLayout()) { final int taskCount = mStack.getTaskCount(); - final int currentIndex = mStack.indexOfStackTask(getFocusedTask()); + final int currentIndex = mStack.indexOfTask(getFocusedTask()); final int nextIndex = mLayoutAlgorithm.mTaskGridLayoutAlgorithm.navigateFocus(taskCount, currentIndex, event.direction); setFocusedTask(nextIndex, false, true); @@ -2003,8 +1868,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal EventBus.getDefault().send(new FocusPreviousTaskViewEvent()); break; case DOWN: - EventBus.getDefault().send( - new FocusNextTaskViewEvent(0 /* timerIndicatorDuration */)); + EventBus.getDefault().send(new FocusNextTaskViewEvent()); break; } } @@ -2015,7 +1879,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mUIDozeTrigger.poke(); RecentsDebugFlags debugFlags = Recents.getDebugFlags(); - if (debugFlags.isFastToggleRecentsEnabled() && mFocusedTask != null) { + if (mFocusedTask != null) { TaskView tv = getChildViewForTask(mFocusedTask); if (tv != null) { tv.getHeaderView().cancelFocusTimerIndicator(); @@ -2027,11 +1891,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Ensure that the drag task is not animated addIgnoreTask(event.task); - if (event.task.isFreeformTask()) { - // Animate to the front of the stack - mStackScroller.animateScroll(mLayoutAlgorithm.mInitialScrollP, null); - } - // Enlarge the dragged view slightly float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR; mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(), @@ -2043,22 +1902,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal new AnimationProps(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN)); } - public final void onBusEvent(DragStartInitializeDropTargetsEvent event) { - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.hasFreeformWorkspaceSupport()) { - event.handler.registerDropTargetForCurrentDrag(mStackDropTarget); - event.handler.registerDropTargetForCurrentDrag(mFreeformWorkspaceDropTarget); - } - } - public final void onBusEvent(DragDropTargetChangedEvent event) { AnimationProps animation = new AnimationProps(SLOW_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN); boolean ignoreTaskOverrides = false; - if (event.dropTarget instanceof TaskStack.DockState) { + if (event.dropTarget instanceof DockState) { // Calculate the new task stack bounds that matches the window size that Recents will // have after the drop - final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; + final DockState dockState = (DockState) event.dropTarget; Rect systemInsets = new Rect(mStableLayoutAlgorithm.mSystemInsets); // When docked, the nav bar insets are consumed and the activity is measured without // insets. However, the window bounds include the insets, so we need to subtract them @@ -2070,8 +1921,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal height, mDividerSize, systemInsets, mLayoutAlgorithm, getResources(), mWindowRect)); mLayoutAlgorithm.setSystemInsets(systemInsets); - mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds, - TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); + mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds); updateLayoutAlgorithm(true /* boundScroll */); ignoreTaskOverrides = true; } else { @@ -2086,39 +1936,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public final void onBusEvent(final DragEndEvent event) { // We don't handle drops on the dock regions - if (event.dropTarget instanceof TaskStack.DockState) { + if (event.dropTarget instanceof DockState) { // However, we do need to reset the overrides, since the last state of this task stack // view layout was ignoring task overrides (see DragDropTargetChangedEvent handler) mLayoutAlgorithm.clearUnfocusedTaskOverrides(); return; } - boolean isFreeformTask = event.task.isFreeformTask(); - boolean hasChangedStacks = - (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) || - (isFreeformTask && event.dropTarget == mStackDropTarget); - - if (hasChangedStacks) { - // Move the task to the right position in the stack (ie. the front of the stack if - // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks - // before we update their stack ids, otherwise, the keys will have changed. - if (event.dropTarget == mFreeformWorkspaceDropTarget) { - mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID); - } else if (event.dropTarget == mStackDropTarget) { - mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID); - } - updateLayoutAlgorithm(true /* boundScroll */); - - // Move the task to the new stack in the system after the animation completes - event.addPostAnimationCallback(new Runnable() { - @Override - public void run() { - SystemServicesProxy ssp = Recents.getSystemServices(); - ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId); - } - }); - } - // Restore the task, so that relayout will apply to it below removeIgnoreTask(event.task); @@ -2153,13 +1977,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal event.getAnimationTrigger().increment(); } - public final void onBusEvent(IterateRecentsEvent event) { - if (!mEnterAnimationComplete) { - // Cancel the previous task's window transition before animating the focused state - EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null)); - } - } - public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) { mEnterAnimationComplete = true; tryStartEnterAnimation(); @@ -2178,9 +1995,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Add a runnable to the post animation ref counter to clear all the views trigger.addLastDecrementRunnable(() -> { // Start the dozer to trigger to trigger any UI that shows after a timeout - if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) { - mUIDozeTrigger.startDozing(); - } + mUIDozeTrigger.startDozing(); // Update the focused state here -- since we only set the focused task without // requesting view focus in onFirstLayout(), actually request view focus and @@ -2189,7 +2004,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (mFocusedTask != null) { RecentsConfiguration config = Recents.getConfiguration(); RecentsActivityLaunchState launchState = config.getLaunchState(); - setFocusedTask(mStack.indexOfStackTask(mFocusedTask), + setFocusedTask(mStack.indexOfTask(mFocusedTask), false /* scrollToTask */, launchState.launchedWithAltTab); TaskView focusedTaskView = getChildViewForTask(mFocusedTask); if (mTouchExplorationEnabled && focusedTaskView != null) { @@ -2203,18 +2018,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mStackReloaded = false; } - public final void onBusEvent(UpdateFreeformTaskViewVisibilityEvent event) { - List<TaskView> taskViews = getTaskViews(); - int taskViewCount = taskViews.size(); - for (int i = 0; i < taskViewCount; i++) { - TaskView tv = taskViews.get(i); - Task task = tv.getTask(); - if (task.isFreeformTask()) { - tv.setVisibility(event.visible ? View.VISIBLE : View.INVISIBLE); - } - } - } - public final void onBusEvent(final MultiWindowStateChangedEvent event) { if (event.inMultiWindow || !event.showDeferredAnimation) { setTasks(event.stack, true /* allowNotifyStackChanges */); @@ -2263,8 +2066,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Update the Clear All button in case we're switching in or out of grid layout. updateStackActionButtonVisibility(); - // Trigger a new layout and update to the initial state if necessary - if (event.fromMultiWindow) { + // Trigger a new layout and update to the initial state if necessary. When entering split + // screen, the multi-window configuration change event can happen after the stack is already + // reloaded (but pending measure/layout), in this case, do not override the intiial state + // and just wait for the upcoming measure/layout pass. + if (event.fromMultiWindow && mInitialState == INITIAL_STATE_UPDATE_NONE) { mInitialState = INITIAL_STATE_UPDATE_LAYOUT_ONLY; requestLayout(); } else if (event.fromDeviceOrientationChange) { @@ -2316,27 +2122,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** - * Starts an alpha animation on the freeform workspace background. - */ - private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha, - AnimationProps animation) { - if (mFreeformWorkspaceBackground.getAlpha() == targetAlpha) { - return; - } - - Utilities.cancelAnimationWithoutCallbacks(mFreeformWorkspaceBackgroundAnimator); - mFreeformWorkspaceBackgroundAnimator = ObjectAnimator.ofInt(mFreeformWorkspaceBackground, - Utilities.DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha); - mFreeformWorkspaceBackgroundAnimator.setStartDelay( - animation.getDuration(AnimationProps.ALPHA)); - mFreeformWorkspaceBackgroundAnimator.setDuration( - animation.getDuration(AnimationProps.ALPHA)); - mFreeformWorkspaceBackgroundAnimator.setInterpolator( - animation.getInterpolator(AnimationProps.ALPHA)); - mFreeformWorkspaceBackgroundAnimator.start(); - } - - /** * Returns the insert index for the task in the current set of task views. If the given task * is already in the task view list, then this method returns the insert index assuming it * is first removed at the previous index. @@ -2353,7 +2138,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal Task tvTask = taskViews.get(i).getTask(); if (tvTask == task) { foundTaskView = true; - } else if (taskIndex < mStack.indexOfStackTask(tvTask)) { + } else if (taskIndex < mStack.indexOfTask(tvTask)) { if (foundTaskView) { return i - 1; } else { @@ -2381,12 +2166,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void run() { EventBus.getDefault().send(new LaunchTaskEvent( getChildViewForTask(task), task, null, - INVALID_STACK_ID, false /* screenPinningRequested */)); + false /* screenPinningRequested */)); } }); } else { - EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(task), - task, null, INVALID_STACK_ID, false /* screenPinningRequested */)); + EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(task), task, null, + false /* screenPinningRequested */)); } } @@ -2403,8 +2188,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal private void readSystemFlags() { SystemServicesProxy ssp = Recents.getSystemServices(); mTouchExplorationEnabled = ssp.isTouchExplorationEnabled(); - mScreenPinningEnabled = ssp.getSystemSetting(getContext(), - Settings.System.LOCK_TO_APP_ENABLED) != 0; + mScreenPinningEnabled = ActivityManagerWrapper.getInstance().isLockToAppEnabled(); } private void updateStackActionButtonVisibility() { @@ -2422,6 +2206,43 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } + /** + * Returns the task to focus given the current launch state. + */ + private int getInitialFocusTaskIndex(RecentsActivityLaunchState launchState, int numTasks, + boolean useGridLayout) { + if (launchState.launchedFromApp) { + if (useGridLayout) { + // If coming from another app to the grid layout, focus the front most task + return numTasks - 1; + } + + // If coming from another app, focus the next task + return Math.max(0, numTasks - 2); + } else { + // If coming from home, focus the front most task + return numTasks - 1; + } + } + + /** + * Updates {@param transforms} to be the same size as {@param tasks}. + */ + private void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) { + // We can reuse the task transforms where possible to reduce object allocation + int taskTransformCount = transforms.size(); + int taskCount = tasks.size(); + if (taskTransformCount < taskCount) { + // If there are less transforms than tasks, then add as many transforms as necessary + for (int i = taskTransformCount; i < taskCount; i++) { + transforms.add(new TaskViewTransform()); + } + } else if (taskTransformCount > taskCount) { + // If there are more transforms than tasks, then just subset the transform list + transforms.subList(taskCount, taskTransformCount).clear(); + } + } + public void dump(String prefix, PrintWriter writer) { String innerPrefix = prefix + " "; String id = Integer.toHexString(System.identityHashCode(this)); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java index 0b20b105617d..6b23977410c7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java @@ -24,7 +24,6 @@ import android.animation.ValueAnimator; import android.content.Context; import android.util.FloatProperty; import android.util.Log; -import android.util.MutableFloat; import android.util.Property; import android.view.ViewConfiguration; import android.view.ViewDebug; @@ -33,7 +32,8 @@ import android.widget.OverScroller; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.recents.Recents; -import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.shared.recents.utilities.AnimationProps; +import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm; import com.android.systemui.statusbar.FlingAnimationUtils; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index 32a249c289c6..c91cdfc379c4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -21,7 +21,6 @@ import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.graphics.Path; -import android.graphics.Rect; import android.util.ArrayMap; import android.util.MutableBoolean; import android.view.InputDevice; @@ -45,9 +44,9 @@ import com.android.systemui.recents.events.activity.HideRecentsEvent; import com.android.systemui.recents.events.ui.StackViewScrolledEvent; import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; import com.android.systemui.recents.misc.FreePathInterpolator; -import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.Utilities; -import com.android.systemui.recents.model.Task; +import com.android.systemui.shared.recents.utilities.AnimationProps; +import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.recents.model.Task; import com.android.systemui.statusbar.FlingAnimationUtils; import java.util.ArrayList; @@ -257,6 +256,9 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { } case MotionEvent.ACTION_MOVE: { int activePointerIndex = ev.findPointerIndex(mActivePointerId); + if (activePointerIndex == -1) { + break; + } int y = (int) ev.getY(activePointerIndex); int x = (int) ev.getX(activePointerIndex); if (!mIsScrolling) { @@ -403,18 +405,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { return; } - // If tapping on the freeform workspace background, just launch the first freeform task - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.hasFreeformWorkspaceSupport()) { - Rect freeformRect = mSv.mLayoutAlgorithm.mFreeformRect; - if (freeformRect.top <= y && y <= freeformRect.bottom) { - if (mSv.launchFreeformTasks()) { - // TODO: Animate Recents away as we launch the freeform tasks - return; - } - } - } - // The user intentionally tapped on the background, which is like a tap on the "desktop". // Hide recents and transition to the launcher. EventBus.getDefault().send(new HideRecentsEvent(false, true)); @@ -459,7 +449,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { TaskView tv = (TaskView) v; Task task = tv.getTask(); return !mSwipeHelperAnimations.containsKey(v) && - (mSv.getStack().indexOfStackTask(task) != -1); + (mSv.getStack().indexOfTask(task) != -1); } /** @@ -489,7 +479,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { mSv.addIgnoreTask(tv.getTask()); // Determine if we are animating the other tasks while dismissing this task - mCurrentTasks = new ArrayList<Task>(mSv.getStack().getStackTasks()); + mCurrentTasks = new ArrayList<Task>(mSv.getStack().getTasks()); MutableBoolean isFrontMostTask = new MutableBoolean(false); Task anchorTask = mSv.findAnchorTask(mCurrentTasks, isFrontMostTask); TaskStackLayoutAlgorithm layoutAlgorithm = mSv.getStackAlgorithm(); @@ -686,7 +676,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { /** Returns the view at the specified coordinates */ private TaskView findViewAtPoint(int x, int y) { - List<Task> tasks = mSv.getStack().getStackTasks(); + List<Task> tasks = mSv.getStack().getTasks(); int taskCount = tasks.size(); for (int i = taskCount - 1; i >= 0; i--) { TaskView tv = mSv.getChildViewForTask(tasks.get(i)); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index ceeebd96a3e1..f0278a69e116 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -16,8 +16,6 @@ package com.android.systemui.recents.views; -import static android.app.ActivityManager.StackId.INVALID_STACK_ID; - import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; @@ -53,10 +51,11 @@ import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.Utilities; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.TaskStack; -import com.android.systemui.recents.model.ThumbnailData; +import com.android.systemui.shared.recents.utilities.AnimationProps; +import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.systemui.shared.recents.view.AnimateableViewBounds; import java.io.PrintWriter; import java.util.ArrayList; @@ -196,9 +195,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks * Called from RecentsActivity when it is relaunched. */ void onReload(boolean isResumingFromVisible) { - if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) { - resetNoUserInteractionState(); - } + resetNoUserInteractionState(); if (!isResumingFromVisible) { resetViewProperties(); } @@ -415,9 +412,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks * view. */ boolean shouldClipViewInStack() { - // Never clip for freeform tasks or if invisible - if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE || - Recents.getConfiguration().isLowRamDevice) { + if (getVisibility() != View.VISIBLE || Recents.getConfiguration().isLowRamDevice) { return false; } return mClipViewInStack; @@ -647,7 +642,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks } @Override - public void onTaskStackIdChanged() { + public void onTaskWindowingModeChanged() { // Force rebind the header, the thumbnail does not change due to stack changes mHeaderView.bindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode); mHeaderView.onTaskDataLoaded(); @@ -674,8 +669,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks mActionButtonView.setTranslationZ(0f); screenPinningRequested = true; } - EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, INVALID_STACK_ID, - screenPinningRequested)); + EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, screenPinningRequested)); MetricsLogger.action(v.getContext(), MetricsEvent.ACTION_OVERVIEW_SELECT, mTask.key.getComponent().toString()); @@ -690,7 +684,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks } SystemServicesProxy ssp = Recents.getSystemServices(); boolean inBounds = false; - Rect clipBounds = new Rect(mViewBounds.mClipBounds); + Rect clipBounds = new Rect(mViewBounds.getClipBounds()); if (!clipBounds.isEmpty()) { // If we are clipping the view to the bounds, manually do the hit test. clipBounds.scale(getScaleX()); @@ -716,7 +710,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks /**** Events ****/ public final void onBusEvent(DragEndEvent event) { - if (!(event.dropTarget instanceof TaskStack.DockState)) { + if (!(event.dropTarget instanceof DockState)) { event.addPostAnimationCallback(() -> { // Reset the clip state for the drag view after the end animation completes setClipViewInStack(true); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java index 0c6b6b842655..0fc507b92bf3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java @@ -28,11 +28,10 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import com.android.systemui.R; import com.android.systemui.recents.Recents; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; -import com.android.systemui.recents.misc.Utilities; -import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.recents.model.TaskStack; public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate { private static final String TAG = "TaskViewAccessibilityDelegate"; @@ -61,14 +60,14 @@ public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate { super.onInitializeAccessibilityNodeInfo(host, info); if (ActivityManager.supportsSplitScreenMultiWindow(mTaskView.getContext()) && !Recents.getSystemServices().hasDockedTask()) { - TaskStack.DockState[] dockStates = Recents.getConfiguration() + DockState[] dockStates = Recents.getConfiguration() .getDockStatesForCurrentOrientation(); - for (TaskStack.DockState dockState: dockStates) { - if (dockState == TaskStack.DockState.TOP) { + for (DockState dockState: dockStates) { + if (dockState == DockState.TOP) { info.addAction(mActions.get(SPLIT_TASK_TOP)); - } else if (dockState == TaskStack.DockState.LEFT) { + } else if (dockState == DockState.LEFT) { info.addAction(mActions.get(SPLIT_TASK_LEFT)); - } else if (dockState == TaskStack.DockState.RIGHT) { + } else if (dockState == DockState.RIGHT) { info.addAction(mActions.get(SPLIT_TASK_RIGHT)); } } @@ -78,11 +77,11 @@ public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate { @Override public boolean performAccessibilityAction(View host, int action, Bundle args) { if (action == SPLIT_TASK_TOP) { - simulateDragIntoMultiwindow(TaskStack.DockState.TOP); + simulateDragIntoMultiwindow(DockState.TOP); } else if (action == SPLIT_TASK_LEFT) { - simulateDragIntoMultiwindow(TaskStack.DockState.LEFT); + simulateDragIntoMultiwindow(DockState.LEFT); } else if (action == SPLIT_TASK_RIGHT) { - simulateDragIntoMultiwindow(TaskStack.DockState.RIGHT); + simulateDragIntoMultiwindow(DockState.RIGHT); } else { return super.performAccessibilityAction(host, action, args); } @@ -90,8 +89,7 @@ public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate { } /** Simulate a user drag event to split the screen to the respected side */ - private void simulateDragIntoMultiwindow(TaskStack.DockState dockState) { - int orientation = Utilities.getAppConfiguration(mTaskView.getContext()).orientation; + private void simulateDragIntoMultiwindow(DockState dockState) { EventBus.getDefault().send(new DragStartEvent(mTaskView.getTask(), mTaskView, new Point(0,0), false /* isUserTouchInitiated */)); EventBus.getDefault().send(new DragEndEvent(mTaskView.getTask(), mTaskView, dockState)); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index ae922fcc218e..0272a9038ba8 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -16,6 +16,9 @@ package com.android.systemui.recents.views; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.Nullable; @@ -28,7 +31,6 @@ import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; -import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; @@ -54,12 +56,10 @@ import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.LaunchTaskEvent; import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.Utilities; -import com.android.systemui.recents.model.Task; - -import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; -import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; -import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.PackageManagerWrapper; +import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.recents.model.Task; /* The task bar view */ public class TaskViewHeader extends FrameLayout @@ -163,8 +163,6 @@ public class TaskViewHeader extends FrameLayout float mDimAlpha; Drawable mLightDismissDrawable; Drawable mDarkDismissDrawable; - Drawable mLightFreeformIcon; - Drawable mDarkFreeformIcon; Drawable mLightFullscreenIcon; Drawable mDarkFullscreenIcon; Drawable mLightInfoIcon; @@ -172,7 +170,9 @@ public class TaskViewHeader extends FrameLayout int mTaskBarViewLightTextColor; int mTaskBarViewDarkTextColor; int mDisabledTaskBarBackgroundColor; - int mMoveTaskTargetStackId = INVALID_STACK_ID; + String mDismissDescFormat; + String mAppInfoDescFormat; + int mTaskWindowingMode = WINDOWING_MODE_UNDEFINED; // Header background private HighlightColorDrawable mBackground; @@ -214,14 +214,15 @@ public class TaskViewHeader extends FrameLayout mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight); mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color); mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color); - mLightFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_light); - mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark); mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light); mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark); mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light); mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark); mDisabledTaskBarBackgroundColor = context.getColor(R.color.recents_task_bar_disabled_background_color); + mDismissDescFormat = mContext.getString( + R.string.accessibility_recents_item_will_be_dismissed); + mAppInfoDescFormat = mContext.getString(R.string.accessibility_recents_item_open_app_info); // Configure the background and dim mBackground = new HighlightColorDrawable(); @@ -248,9 +249,6 @@ public class TaskViewHeader extends FrameLayout mIconView.setOnLongClickListener(this); mTitleView = findViewById(R.id.title); mDismissButton = findViewById(R.id.dismiss_task); - if (ssp.hasFreeformWorkspaceSupport()) { - mMoveTaskButton = findViewById(R.id.move_task); - } onConfigurationChanged(); } @@ -340,20 +338,6 @@ public class TaskViewHeader extends FrameLayout boolean showDismissIcon = true; int rightInset = width - getMeasuredWidth(); - if (mTask != null && mTask.isFreeformTask()) { - // For freeform tasks, we always show the app icon, and only show the title, move-task - // icon, and the dismiss icon if there is room - int appIconWidth = mIconView.getMeasuredWidth(); - int titleWidth = (int) mTitleView.getPaint().measureText(mTask.title); - int dismissWidth = mDismissButton.getMeasuredWidth(); - int moveTaskWidth = mMoveTaskButton != null - ? mMoveTaskButton.getMeasuredWidth() - : 0; - showTitle = width >= (appIconWidth + dismissWidth + moveTaskWidth + titleWidth); - showMoveIcon = width >= (appIconWidth + dismissWidth + moveTaskWidth); - showDismissIcon = width >= (appIconWidth + dismissWidth); - } - mTitleView.setVisibility(showTitle ? View.VISIBLE : View.INVISIBLE); if (mMoveTaskButton != null) { mMoveTaskButton.setVisibility(showMoveIcon ? View.VISIBLE : View.INVISIBLE); @@ -476,44 +460,14 @@ public class TaskViewHeader extends FrameLayout mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor); mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ? mLightDismissDrawable : mDarkDismissDrawable); - mDismissButton.setContentDescription(t.dismissDescription); + mDismissButton.setContentDescription(String.format(mDismissDescFormat, t.titleDescription)); mDismissButton.setOnClickListener(this); mDismissButton.setClickable(false); ((RippleDrawable) mDismissButton.getBackground()).setForceSoftware(true); - // When freeform workspaces are enabled, then update the move-task button depending on the - // current task - if (mMoveTaskButton != null) { - if (t.isFreeformTask()) { - mMoveTaskTargetStackId = FULLSCREEN_WORKSPACE_STACK_ID; - mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor - ? mLightFullscreenIcon - : mDarkFullscreenIcon); - } else { - mMoveTaskTargetStackId = FREEFORM_WORKSPACE_STACK_ID; - mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor - ? mLightFreeformIcon - : mDarkFreeformIcon); - } - mMoveTaskButton.setOnClickListener(this); - mMoveTaskButton.setClickable(false); - ((RippleDrawable) mMoveTaskButton.getBackground()).setForceSoftware(true); - } - - if (Recents.getDebugFlags().isFastToggleRecentsEnabled()) { - if (mFocusTimerIndicator == null) { - mFocusTimerIndicator = (ProgressBar) Utilities.findViewStubById(this, - R.id.focus_timer_indicator_stub).inflate(); - } - mFocusTimerIndicator.getProgressDrawable() - .setColorFilter( - getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor), - PorterDuff.Mode.SRC_IN); - } - // In accessibility, a single click on the focused app info button will show it if (touchExplorationEnabled) { - mIconView.setContentDescription(t.appInfoDescription); + mIconView.setContentDescription(String.format(mAppInfoDescFormat, t.titleDescription)); mIconView.setOnClickListener(this); mIconView.setClickable(true); } @@ -621,8 +575,8 @@ public class TaskViewHeader extends FrameLayout Constants.Metrics.DismissSourceHeaderButton); } else if (v == mMoveTaskButton) { TaskView tv = Utilities.findParent(this, TaskView.class); - EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null, - mMoveTaskTargetStackId, false)); + EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null, false, + mTaskWindowingMode, ACTIVITY_TYPE_UNDEFINED)); } else if (v == mAppInfoView) { EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); } else if (v == mAppIconView) { @@ -650,7 +604,7 @@ public class TaskViewHeader extends FrameLayout SystemServicesProxy ssp = Recents.getSystemServices(); ComponentName cn = mTask.key.getComponent(); int userId = mTask.key.userId; - ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId); + ActivityInfo activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(cn, userId); if (activityInfo == null) { return; } @@ -670,11 +624,12 @@ public class TaskViewHeader extends FrameLayout } // Update the overlay contents for the current app - mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId)); + mAppTitleView.setText(ActivityManagerWrapper.getInstance().getBadgedApplicationLabel( + activityInfo.applicationInfo, userId)); mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ? mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor); - mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo, - userId)); + mAppIconView.setImageDrawable(ActivityManagerWrapper.getInstance().getBadgedApplicationIcon( + activityInfo.applicationInfo, userId)); mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor ? mLightInfoIcon : mDarkInfoIcon); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java index a2190b3a3d19..4152b05a960e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java @@ -37,9 +37,9 @@ import android.view.ViewDebug; import com.android.systemui.R; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent; -import com.android.systemui.recents.misc.Utilities; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.ThumbnailData; +import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.ThumbnailData; import java.io.PrintWriter; @@ -245,10 +245,6 @@ public class TaskViewThumbnail extends View { public void updateThumbnailMatrix() { mThumbnailScale = 1f; if (mBitmapShader != null && mThumbnailData != null) { - // We consider this a stack task if it is not freeform (ie. has no bounds) or has been - // dragged into the stack from the freeform workspace - boolean isStackTask = !mTask.isFreeformTask() || mTask.bounds == null; - int xOffset, yOffset = 0; if (mTaskViewRect.isEmpty()) { // If we haven't measured , skip the thumbnail drawing and only draw the background // color @@ -266,7 +262,7 @@ public class TaskViewThumbnail extends View { mThumbnailScale = (float) (mTaskViewRect.height() - mTitleBarHeight) / (float) mThumbnailRect.height(); } - } else if (isStackTask) { + } else { float invThumbnailScale = 1f / mFullscreenThumbnailScale; if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) { if (mThumbnailData.orientation == Configuration.ORIENTATION_PORTRAIT) { @@ -283,12 +279,6 @@ public class TaskViewThumbnail extends View { // Otherwise, scale the screenshot to fit 1:1 in the current orientation mThumbnailScale = invThumbnailScale; } - } else { - // Otherwise, if this is a freeform task with task bounds, then scale the thumbnail - // to fit the entire bitmap into the task bounds - mThumbnailScale = Math.min( - (float) mTaskViewRect.width() / mThumbnailRect.width(), - (float) mTaskViewRect.height() / mThumbnailRect.height()); } mMatrix.setTranslate(-mThumbnailData.insets.left * mFullscreenThumbnailScale, -mThumbnailData.insets.top * mFullscreenThumbnailScale); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java index 397f24eb86d3..9b717e0e5e2f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java @@ -21,11 +21,11 @@ import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.graphics.Rect; import android.graphics.RectF; -import android.util.IntProperty; import android.util.Property; import android.view.View; -import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.shared.recents.utilities.AnimationProps; +import com.android.systemui.shared.recents.utilities.Utilities; import java.util.ArrayList; @@ -59,7 +59,7 @@ public class TaskViewTransform { public boolean visible = false; - // This is a window-space rect used for positioning the task in the stack and freeform workspace + // This is a window-space rect used for positioning the task in the stack public RectF rect = new RectF(); /** diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java index a029478c2045..3bdad314d7f3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.views.grid; import android.view.View; -import com.android.systemui.recents.views.AnimateableViewBounds; +import com.android.systemui.shared.recents.view.AnimateableViewBounds; /* An outline provider for grid-based task views. */ class AnimateableGridViewBounds extends AnimateableViewBounds { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java index 8b4700c54b00..0d5115444b01 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java @@ -19,7 +19,7 @@ package com.android.systemui.recents.views.grid; import android.content.Context; import android.util.AttributeSet; import com.android.systemui.R; -import com.android.systemui.recents.views.AnimateableViewBounds; +import com.android.systemui.shared.recents.view.AnimateableViewBounds; import com.android.systemui.recents.views.TaskView; public class GridTaskView extends TaskView { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java index c5132024d505..ccda4b5aaf1f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java @@ -25,10 +25,9 @@ import android.graphics.Rect; import android.view.WindowManager; import com.android.systemui.R; -import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent; import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction; -import com.android.systemui.recents.misc.Utilities; -import com.android.systemui.recents.model.Task; +import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.recents.model.Task; import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; import com.android.systemui.recents.views.TaskViewTransform; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java index 86ed583b07aa..fe6bafb27c7c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java @@ -23,7 +23,7 @@ import android.view.View; import android.view.ViewTreeObserver.OnGlobalFocusChangeListener; import com.android.systemui.R; -import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.shared.recents.model.TaskStack; import com.android.systemui.recents.views.TaskStackView; public class TaskViewFocusFrame extends View implements OnGlobalFocusChangeListener { @@ -125,7 +125,7 @@ public class TaskViewFocusFrame extends View implements OnGlobalFocusChangeListe // We're returning from touch mode, set the focus to the previously focused task. final TaskStack stack = mSv.getStack(); final int taskCount = stack.getTaskCount(); - final int k = stack.indexOfStackTask(mSv.getFocusedTask()); + final int k = stack.indexOfTask(mSv.getFocusedTask()); final int taskIndexToFocus = k == -1 ? (taskCount - 1) : (k % taskCount); mSv.setFocusedTask(taskIndexToFocus, false, true); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java index 17e6b9e3c195..49cac269f51d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java @@ -23,8 +23,8 @@ import android.view.ViewConfiguration; import com.android.systemui.R; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsActivityLaunchState; -import com.android.systemui.recents.misc.Utilities; -import com.android.systemui.recents.model.Task; +import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.recents.model.Task; import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; import com.android.systemui.recents.views.TaskViewTransform; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 991c3c83cbc1..2acb1bb2613b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -43,13 +43,13 @@ import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.Picture; import android.graphics.PixelFormat; import android.graphics.PointF; import android.graphics.Rect; import android.media.MediaActionSound; import android.net.Uri; import android.os.AsyncTask; -import android.os.Bundle; import android.os.Environment; import android.os.PowerManager; import android.os.Process; @@ -154,7 +154,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { int previewWidth = data.previewWidth; int previewHeight = data.previewheight; - Canvas c = new Canvas(); Paint paint = new Paint(); ColorMatrix desat = new ColorMatrix(); desat.setSaturation(0.25f); @@ -162,23 +161,17 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Matrix matrix = new Matrix(); int overlayColor = 0x40FFFFFF; - Bitmap picture = Bitmap.createBitmap(previewWidth, previewHeight, data.image.getConfig()); matrix.setTranslate((previewWidth - mImageWidth) / 2, (previewHeight - mImageHeight) / 2); - c.setBitmap(picture); - c.drawBitmap(data.image, matrix, paint); - c.drawColor(overlayColor); - c.setBitmap(null); + Bitmap picture = generateAdjustedHwBitmap(data.image, previewWidth, previewHeight, matrix, + paint, overlayColor); // Note, we can't use the preview for the small icon, since it is non-square float scale = (float) iconSize / Math.min(mImageWidth, mImageHeight); - Bitmap icon = Bitmap.createBitmap(iconSize, iconSize, data.image.getConfig()); matrix.setScale(scale, scale); matrix.postTranslate((iconSize - (scale * mImageWidth)) / 2, (iconSize - (scale * mImageHeight)) / 2); - c.setBitmap(icon); - c.drawBitmap(data.image, matrix, paint); - c.drawColor(overlayColor); - c.setBitmap(null); + Bitmap icon = generateAdjustedHwBitmap(data.image, iconSize, iconSize, matrix, paint, + overlayColor); // Show the intermediate notification mTickerAddSpace = !mTickerAddSpace; @@ -191,7 +184,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // The public notification will show similar info but with the actual screenshot omitted mPublicNotificationBuilder = - new Notification.Builder(context, NotificationChannels.SCREENSHOTS) + new Notification.Builder(context, NotificationChannels.SCREENSHOTS_HEADSUP) .setContentTitle(r.getString(R.string.screenshot_saving_title)) .setContentText(r.getString(R.string.screenshot_saving_text)) .setSmallIcon(R.drawable.stat_notify_image) @@ -202,7 +195,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { com.android.internal.R.color.system_notification_accent_color)); SystemUI.overrideNotificationAppName(context, mPublicNotificationBuilder); - mNotificationBuilder = new Notification.Builder(context, NotificationChannels.SCREENSHOTS) + mNotificationBuilder = new Notification.Builder(context, + NotificationChannels.SCREENSHOTS_HEADSUP) .setTicker(r.getString(R.string.screenshot_saving_ticker) + (mTickerAddSpace ? " " : "")) .setContentTitle(r.getString(R.string.screenshot_saving_title)) @@ -232,6 +226,20 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mNotificationStyle.bigLargeIcon((Bitmap) null); } + /** + * Generates a new hardware bitmap with specified values, copying the content from the passed + * in bitmap. + */ + private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix, + Paint paint, int color) { + Picture picture = new Picture(); + Canvas canvas = picture.beginRecording(width, height); + canvas.drawColor(color); + canvas.drawBitmap(bitmap, matrix, paint); + picture.endRecording(); + return Bitmap.createBitmap(picture); + } + @Override protected Void doInBackground(Void... params) { if (isCancelled()) { @@ -282,13 +290,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { sharingIntent.setType("image/png"); sharingIntent.putExtra(Intent.EXTRA_STREAM, uri); sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject); + sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - // Create a share action for the notification. Note, we proxy the call to ShareReceiver - // because RemoteViews currently forces an activity options on the PendingIntent being - // launched, and since we don't want to trigger the share sheet in this case, we will - // start the chooser activitiy directly in ShareReceiver. + // Create a share action for the notification. Note, we proxy the call to + // ScreenshotActionReceiver because RemoteViews currently forces an activity options + // on the PendingIntent being launched, and since we don't want to trigger the share + // sheet in this case, we start the chooser activity directly in + // ScreenshotActionReceiver. PendingIntent shareAction = PendingIntent.getBroadcast(context, 0, - new Intent(context, GlobalScreenshot.ShareReceiver.class) + new Intent(context, GlobalScreenshot.ScreenshotActionReceiver.class) .putExtra(SHARING_INTENT, sharingIntent), PendingIntent.FLAG_CANCEL_CURRENT); Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder( @@ -296,15 +306,21 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { r.getString(com.android.internal.R.string.share), shareAction); mNotificationBuilder.addAction(shareActionBuilder.build()); - // Create a delete action for the notification - PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0, - new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class) - .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()), - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); - Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( - R.drawable.ic_screenshot_delete, - r.getString(com.android.internal.R.string.delete), deleteAction); - mNotificationBuilder.addAction(deleteActionBuilder.build()); + Intent editIntent = new Intent(Intent.ACTION_EDIT); + editIntent.setType("image/png"); + editIntent.setData(uri); + editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + + // Create a edit action for the notification the same way. + PendingIntent editAction = PendingIntent.getBroadcast(context, 1, + new Intent(context, GlobalScreenshot.ScreenshotActionReceiver.class) + .putExtra(SHARING_INTENT, editIntent), + PendingIntent.FLAG_CANCEL_CURRENT); + Notification.Action.Builder editActionBuilder = new Notification.Action.Builder( + R.drawable.ic_screenshot_edit, + r.getString(com.android.internal.R.string.screenshot_edit), editAction); + mNotificationBuilder.addAction(editActionBuilder.build()); mParams.imageUri = uri; mParams.image = null; @@ -557,25 +573,14 @@ class GlobalScreenshot { /** * Takes a screenshot of the current display and shows an animation. */ - void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible, - int x, int y, int width, int height) { - // We need to orient the screenshot correctly (and the Surface api seems to take screenshots - // only in the natural orientation of the device :!) - mDisplay.getRealMetrics(mDisplayMetrics); - float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels}; - float degrees = getDegreesForRotation(mDisplay.getRotation()); - boolean requiresRotation = (degrees > 0); - if (requiresRotation) { - // Get the dimensions of the device in its native orientation - mDisplayMatrix.reset(); - mDisplayMatrix.preRotate(-degrees); - mDisplayMatrix.mapPoints(dims); - dims[0] = Math.abs(dims[0]); - dims[1] = Math.abs(dims[1]); - } + private void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible, + Rect crop) { + int rot = mDisplay.getRotation(); + int width = crop.width(); + int height = crop.height(); // Take the screenshot - mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]); + mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot); if (mScreenBitmap == null) { notifyScreenshotError(mContext, mNotificationManager, R.string.screenshot_failed_to_capture_text); @@ -583,29 +588,6 @@ class GlobalScreenshot { return; } - if (requiresRotation) { - // Rotate the screenshot to the current orientation - Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels, - mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888, - mScreenBitmap.hasAlpha(), mScreenBitmap.getColorSpace()); - Canvas c = new Canvas(ss); - c.translate(ss.getWidth() / 2, ss.getHeight() / 2); - c.rotate(degrees); - c.translate(-dims[0] / 2, -dims[1] / 2); - c.drawBitmap(mScreenBitmap, 0, 0, null); - c.setBitmap(null); - // Recycle the previous bitmap - mScreenBitmap.recycle(); - mScreenBitmap = ss; - } - - if (width != mDisplayMetrics.widthPixels || height != mDisplayMetrics.heightPixels) { - // Crop the screenshot to selected region - Bitmap cropped = Bitmap.createBitmap(mScreenBitmap, x, y, width, height); - mScreenBitmap.recycle(); - mScreenBitmap = cropped; - } - // Optimizations mScreenBitmap.setHasAlpha(false); mScreenBitmap.prepareToDraw(); @@ -617,8 +599,8 @@ class GlobalScreenshot { void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) { mDisplay.getRealMetrics(mDisplayMetrics); - takeScreenshot(finisher, statusBarVisible, navBarVisible, 0, 0, mDisplayMetrics.widthPixels, - mDisplayMetrics.heightPixels); + takeScreenshot(finisher, statusBarVisible, navBarVisible, + new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); } /** @@ -648,7 +630,7 @@ class GlobalScreenshot { mScreenshotLayout.post(new Runnable() { public void run() { takeScreenshot(finisher, statusBarVisible, navBarVisible, - rect.left, rect.top, rect.width(), rect.height()); + rect); } }); } @@ -903,9 +885,9 @@ class GlobalScreenshot { } /** - * Receiver to proxy the share intent. + * Receiver to proxy the share or edit intent. */ - public static class ShareReceiver extends BroadcastReceiver { + public static class ScreenshotActionReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { try { @@ -920,6 +902,7 @@ class GlobalScreenshot { Intent chooserIntent = Intent.createChooser(sharingIntent, null, chooseAction.getIntentSender()) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + ActivityOptions opts = ActivityOptions.makeBasic(); opts.setDisallowEnterPictureInPictureWhileLaunching(true); context.startActivityAsUser(chooserIntent, opts.toBundle(), UserHandle.CURRENT); @@ -927,7 +910,7 @@ class GlobalScreenshot { } /** - * Removes the notification for a screenshot after a share target is chosen. + * Removes the notification for a screenshot after a share or edit target is chosen. */ public static class TargetChosenReceiver extends BroadcastReceiver { @Override diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index f3bae20e544a..34b8bfe59e7e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -67,6 +67,8 @@ public class TakeScreenshotService extends Service { case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0); break; + default: + Log.d(TAG, "Invalid screenshot option: " + msg.what); } } }; diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java index d3f997a04500..406eef82f737 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java @@ -16,9 +16,11 @@ package com.android.systemui.settings; +import android.animation.ValueAnimator; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; +import android.hardware.display.DisplayManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; @@ -29,6 +31,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; @@ -37,6 +40,7 @@ import android.widget.ImageView; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settingslib.RestrictedLockUtils; import com.android.systemui.Dependency; import java.util.ArrayList; @@ -45,11 +49,7 @@ public class BrightnessController implements ToggleSlider.Listener { private static final String TAG = "StatusBar.BrightnessController"; private static final boolean SHOW_AUTOMATIC_ICON = false; - /** - * {@link android.provider.Settings.System#SCREEN_AUTO_BRIGHTNESS_ADJ} uses the range [-1, 1]. - * Using this factor, it is converted to [0, BRIGHTNESS_ADJ_RESOLUTION] for the SeekBar. - */ - private static final float BRIGHTNESS_ADJ_RESOLUTION = 2048; + private static final int SLIDER_ANIMATION_DURATION = 3000; private static final int MSG_UPDATE_ICON = 0; private static final int MSG_UPDATE_SLIDER = 1; @@ -67,7 +67,7 @@ public class BrightnessController implements ToggleSlider.Listener { private final ImageView mIcon; private final ToggleSlider mControl; private final boolean mAutomaticAvailable; - private final IPowerManager mPower; + private final DisplayManager mDisplayManager; private final CurrentUserTracker mUserTracker; private final IVrManager mVrManager; @@ -81,6 +81,9 @@ public class BrightnessController implements ToggleSlider.Listener { private volatile boolean mIsVrModeEnabled; private boolean mListening; private boolean mExternalChange; + private boolean mControlValueInitialized; + + private ValueAnimator mSliderAnimator; public interface BrightnessStateChangeCallback { public void onBrightnessLevelChanged(); @@ -95,8 +98,6 @@ public class BrightnessController implements ToggleSlider.Listener { Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS); private final Uri BRIGHTNESS_FOR_VR_URI = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR); - private final Uri BRIGHTNESS_ADJ_URI = - Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ); public BrightnessObserver(Handler handler) { super(handler); @@ -114,12 +115,10 @@ public class BrightnessController implements ToggleSlider.Listener { if (BRIGHTNESS_MODE_URI.equals(uri)) { mBackgroundHandler.post(mUpdateModeRunnable); mBackgroundHandler.post(mUpdateSliderRunnable); - } else if (BRIGHTNESS_URI.equals(uri) && !mAutomatic) { + } else if (BRIGHTNESS_URI.equals(uri)) { mBackgroundHandler.post(mUpdateSliderRunnable); } else if (BRIGHTNESS_FOR_VR_URI.equals(uri)) { mBackgroundHandler.post(mUpdateSliderRunnable); - } else if (BRIGHTNESS_ADJ_URI.equals(uri) && mAutomatic) { - mBackgroundHandler.post(mUpdateSliderRunnable); } else { mBackgroundHandler.post(mUpdateModeRunnable); mBackgroundHandler.post(mUpdateSliderRunnable); @@ -141,9 +140,6 @@ public class BrightnessController implements ToggleSlider.Listener { cr.registerContentObserver( BRIGHTNESS_FOR_VR_URI, false, this, UserHandle.USER_ALL); - cr.registerContentObserver( - BRIGHTNESS_ADJ_URI, - false, this, UserHandle.USER_ALL); } public void stopObserving() { @@ -214,12 +210,6 @@ public class BrightnessController implements ToggleSlider.Listener { mHandler.obtainMessage(MSG_UPDATE_SLIDER, mMaximumBacklightForVr - mMinimumBacklightForVr, value - mMinimumBacklightForVr).sendToTarget(); - } else if (mAutomatic) { - float value = Settings.System.getFloatForUser(mContext.getContentResolver(), - Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0, - UserHandle.USER_CURRENT); - mHandler.obtainMessage(MSG_UPDATE_SLIDER, (int) BRIGHTNESS_ADJ_RESOLUTION, - (int) ((value + 1) * BRIGHTNESS_ADJ_RESOLUTION / 2f)).sendToTarget(); } else { int value; value = Settings.System.getIntForUser(mContext.getContentResolver(), @@ -250,7 +240,7 @@ public class BrightnessController implements ToggleSlider.Listener { break; case MSG_UPDATE_SLIDER: mControl.setMax(msg.arg1); - mControl.setValue(msg.arg2); + animateSliderTo(msg.arg2); break; case MSG_SET_CHECKED: mControl.setChecked(msg.arg1 != 0); @@ -295,8 +285,7 @@ public class BrightnessController implements ToggleSlider.Listener { mAutomaticAvailable = context.getResources().getBoolean( com.android.internal.R.bool.config_automatic_brightness_available); - mPower = IPowerManager.Stub.asInterface(ServiceManager.getService( - Context.POWER_SERVICE)); + mDisplayManager = context.getSystemService(DisplayManager.class); mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService( Context.VR_SERVICE)); } @@ -348,6 +337,7 @@ public class BrightnessController implements ToggleSlider.Listener { mBackgroundHandler.post(mStopListeningRunnable); mListening = false; + mControlValueInitialized = false; } @Override @@ -356,6 +346,10 @@ public class BrightnessController implements ToggleSlider.Listener { updateIcon(mAutomatic); if (mExternalChange) return; + if (mSliderAnimator != null) { + mSliderAnimator.cancel(); + } + if (mIsVrModeEnabled) { final int val = value + mMinimumBacklightForVr; if (stopTracking) { @@ -371,7 +365,7 @@ public class BrightnessController implements ToggleSlider.Listener { } }); } - } else if (!mAutomatic) { + } else { final int val = value + mMinimumBacklight; if (stopTracking) { MetricsLogger.action(mContext, MetricsEvent.ACTION_BRIGHTNESS, val); @@ -386,21 +380,6 @@ public class BrightnessController implements ToggleSlider.Listener { } }); } - } else { - final float adj = value / (BRIGHTNESS_ADJ_RESOLUTION / 2f) - 1; - if (stopTracking) { - MetricsLogger.action(mContext, MetricsEvent.ACTION_BRIGHTNESS_AUTO, value); - } - setBrightnessAdj(adj); - if (!tracking) { - AsyncTask.execute(new Runnable() { - public void run() { - Settings.System.putFloatForUser(mContext.getContentResolver(), - Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adj, - UserHandle.USER_CURRENT); - } - }); - } } for (BrightnessStateChangeCallback cb : mChangeCallbacks) { @@ -408,6 +387,18 @@ public class BrightnessController implements ToggleSlider.Listener { } } + public void checkRestrictionAndSetEnabled() { + mBackgroundHandler.post(new Runnable() { + @Override + public void run() { + ((ToggleSliderView)mControl).setEnforcedAdmin( + RestrictedLockUtils.checkIfRestrictionEnforced(mContext, + UserManager.DISALLOW_CONFIG_BRIGHTNESS, + mUserTracker.getCurrentUserId())); + } + }); + } + private void setMode(int mode) { Settings.System.putIntForUser(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, mode, @@ -415,17 +406,11 @@ public class BrightnessController implements ToggleSlider.Listener { } private void setBrightness(int brightness) { - try { - mPower.setTemporaryScreenBrightnessSettingOverride(brightness); - } catch (RemoteException ex) { - } + mDisplayManager.setTemporaryBrightness(brightness); } private void setBrightnessAdj(float adj) { - try { - mPower.setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(adj); - } catch (RemoteException ex) { - } + mDisplayManager.setTemporaryAutoBrightnessAdjustment(adj); } private void updateIcon(boolean automatic) { @@ -442,4 +427,23 @@ public class BrightnessController implements ToggleSlider.Listener { mBackgroundHandler.post(mUpdateSliderRunnable); } } + + private void animateSliderTo(int target) { + if (!mControlValueInitialized) { + // Don't animate the first value since it's default state isn't meaningful to users. + mControl.setValue(target); + mControlValueInitialized = true; + } + if (mSliderAnimator != null && mSliderAnimator.isStarted()) { + mSliderAnimator.cancel(); + } + mSliderAnimator = ValueAnimator.ofInt(mControl.getValue(), target); + mSliderAnimator.addUpdateListener((ValueAnimator animation) -> { + mExternalChange = true; + mControl.setValue((int)animation.getAnimatedValue()); + mExternalChange = false; + }); + mSliderAnimator.setDuration(SLIDER_ANIMATION_DURATION); + mSliderAnimator.start(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java index 722aba591ea8..8ed4c75e8673 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java +++ b/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java @@ -17,14 +17,21 @@ package com.android.systemui.settings; import android.content.Context; +import android.content.Intent; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.SeekBar; +import com.android.settingslib.RestrictedLockUtils; +import com.android.systemui.Dependency; +import com.android.systemui.plugins.ActivityStarter; + public class ToggleSeekBar extends SeekBar { private String mAccessibilityLabel; + private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null; + public ToggleSeekBar(Context context) { super(context); } @@ -39,6 +46,12 @@ public class ToggleSeekBar extends SeekBar { @Override public boolean onTouchEvent(MotionEvent event) { + if (mEnforcedAdmin != null) { + Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent( + mContext, mEnforcedAdmin); + Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0); + return true; + } if (!isEnabled()) { setEnabled(true); } @@ -57,4 +70,8 @@ public class ToggleSeekBar extends SeekBar { info.setText(mAccessibilityLabel); } } + + public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) { + mEnforcedAdmin = admin; + } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java index 62abf3d6c3e2..135f89d3f343 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java +++ b/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java @@ -28,4 +28,5 @@ public interface ToggleSlider { default boolean isChecked() { return false; } void setMax(int max); void setValue(int value); + int getValue(); } diff --git a/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java index 5b234e9c5576..90744a62677d 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java +++ b/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java @@ -29,6 +29,7 @@ import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; +import com.android.settingslib.RestrictedLockUtils; import com.android.systemui.R; import com.android.systemui.statusbar.policy.BrightnessMirrorController; @@ -95,6 +96,12 @@ public class ToggleSliderView extends RelativeLayout implements ToggleSlider { } } + public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) { + mToggle.setEnabled(admin == null); + mSlider.setEnabled(admin == null); + mSlider.setEnforcedAdmin(admin); + } + public void setOnChangedListener(Listener l) { mListener = l; } @@ -126,6 +133,11 @@ public class ToggleSliderView extends RelativeLayout implements ToggleSlider { } @Override + public int getValue() { + return mSlider.getProgress(); + } + + @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mMirror != null) { MotionEvent copy = ev.copy(); diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java index 7699bb90e611..da798848f7e8 100644 --- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java @@ -16,37 +16,31 @@ package com.android.systemui.shortcut; -import android.accessibilityservice.AccessibilityServiceInfo; +import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT; +import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; +import static android.os.UserHandle.USER_CURRENT; + +import static com.android.systemui.statusbar.phone.NavigationBarGestureHelper.DRAG_MODE_NONE; + import android.app.ActivityManager; -import android.app.IActivityManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ServiceInfo; import android.content.res.Configuration; import android.os.RemoteException; -import android.os.UserHandle; -import android.util.ArraySet; -import android.util.DisplayMetrics; import android.util.Log; import android.view.IWindowManager; import android.view.KeyEvent; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.view.accessibility.AccessibilityManager; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + import com.android.internal.policy.DividerSnapAlgorithm; -import com.android.settingslib.accessibility.AccessibilityUtils; -import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.recents.Recents; import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.stackdivider.Divider; import com.android.systemui.stackdivider.DividerView; import com.android.systemui.statusbar.phone.NavigationBarGestureHelper; import java.util.List; -import java.util.Set; /** * Dispatches shortcut to System UI components @@ -58,7 +52,6 @@ public class ShortcutKeyDispatcher extends SystemUI private ShortcutKeyServiceProxy mShortcutKeyServiceProxy = new ShortcutKeyServiceProxy(this); private IWindowManager mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); - private IActivityManager mActivityManager = ActivityManager.getService(); protected final long META_MASK = ((long) KeyEvent.META_META_ON) << Integer.SIZE; protected final long ALT_MASK = ((long) KeyEvent.META_ALT_ON) << Integer.SIZE; @@ -99,21 +92,11 @@ public class ShortcutKeyDispatcher extends SystemUI try { int dockSide = mWindowManagerService.getDockedStackSide(); if (dockSide == WindowManager.DOCKED_INVALID) { - // If there is no window docked, we dock the top-most window. + // Split the screen Recents recents = getComponent(Recents.class); - int dockMode = (shortcutCode == SC_DOCK_LEFT) - ? ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT - : ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; - List<ActivityManager.RecentTaskInfo> taskList = - SystemServicesProxy.getInstance(mContext).getRecentTasks(1, - UserHandle.USER_CURRENT, false, new ArraySet<>()); - recents.showRecentApps( - false /* triggeredFromAltTab */, - false /* fromHome */); - if (!taskList.isEmpty()) { - SystemServicesProxy.getInstance(mContext).startTaskInDockedMode( - taskList.get(0).id, dockMode); - } + recents.splitPrimaryTask(DRAG_MODE_NONE, (shortcutCode == SC_DOCK_LEFT) + ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT + : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1); } else { // If there is already a docked window, we respond by resizing the docking pane. DividerView dividerView = getComponent(Divider.class).getView(); diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java index 23a7dae34cfb..1596d120c16f 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java @@ -16,6 +16,9 @@ package com.android.systemui.stackdivider; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; @@ -23,7 +26,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.Nullable; -import android.app.ActivityManager.StackId; import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; @@ -36,8 +38,6 @@ import android.util.AttributeSet; import android.view.Choreographer; import android.view.Display; import android.view.DisplayInfo; -import android.view.GestureDetector; -import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; import android.view.PointerIcon; import android.view.VelocityTracker; @@ -62,7 +62,6 @@ import com.android.internal.policy.DockedDividerUtils; import com.android.internal.view.SurfaceFlingerVsyncChoreographer; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.recents.Recents; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent; import com.android.systemui.recents.events.activity.DockedTopTaskEvent; @@ -94,7 +93,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, private static final int LOG_VALUE_UNDOCK_MAX_OTHER = 1; private static final int TASK_POSITION_SAME = Integer.MAX_VALUE; - private static final boolean SWAPPING_ENABLED = false; /** * How much the background gets scaled when we are in the minimized dock state. @@ -154,7 +152,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, private boolean mEntranceAnimationRunning; private boolean mExitAnimationRunning; private int mExitStartPosition; - private GestureDetector mGestureDetector; private boolean mDockedStackMinimized; private boolean mHomeStackResizable; private boolean mAdjustedForIme; @@ -296,21 +293,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, landscape ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW)); getViewTreeObserver().addOnComputeInternalInsetsListener(this); mHandle.setAccessibilityDelegate(mHandleDelegate); - mGestureDetector = new GestureDetector(mContext, new SimpleOnGestureListener() { - @Override - public boolean onSingleTapUp(MotionEvent e) { - if (SWAPPING_ENABLED) { - updateDockSide(); - SystemServicesProxy ssp = Recents.getSystemServices(); - if (mDockSide != WindowManager.DOCKED_INVALID - && !ssp.isRecentsActivityVisible()) { - mWindowManagerProxy.swapTasks(); - return true; - } - } - return false; - } - }); } @Override @@ -457,7 +439,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, if (mMinimizedSnapAlgorithm == null) { mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth, mDisplayHeight, mDividerSize, isHorizontalDivision(), - mStableInsets, mDockedStackMinimized && mHomeStackResizable); + mStableInsets, mDockSide, mDockedStackMinimized && mHomeStackResizable); } } @@ -479,7 +461,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, @Override public boolean onTouch(View v, MotionEvent event) { convertToScreenCoordinates(event); - mGestureDetector.onTouchEvent(event); final int action = event.getAction() & MotionEvent.ACTION_MASK; switch (action) { case MotionEvent.ACTION_DOWN: @@ -680,7 +661,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, } else { mWindowManagerProxy.maximizeDockedStack(); } - mWindowManagerProxy.setResizeDimLayer(false, -1, 0f); + mWindowManagerProxy.setResizeDimLayer(false, WINDOWING_MODE_UNDEFINED, 0f); } private void liftBackground() { @@ -1016,8 +997,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position); float dimFraction = getDimFraction(position, closestDismissTarget); mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f, - getStackIdForDismissTarget(closestDismissTarget), - dimFraction); + getWindowingModeForDismissTarget(closestDismissTarget), dimFraction); } private void applyExitAnimationParallax(Rect taskRect, int position) { @@ -1151,13 +1131,13 @@ public class DividerView extends FrameLayout implements OnTouchListener, } } - private int getStackIdForDismissTarget(SnapTarget dismissTarget) { + private int getWindowingModeForDismissTarget(SnapTarget dismissTarget) { if ((dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide)) || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END && dockSideBottomRight(mDockSide))) { - return StackId.DOCKED_STACK_ID; + return WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; } else { - return StackId.RECENTS_STACK_ID; + return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; } } diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java index 578a18a09c6c..826fa6cefccc 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java @@ -31,8 +31,9 @@ import com.android.systemui.R; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent; import com.android.systemui.recents.events.component.ShowUserToastEvent; +import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.stackdivider.events.StartedDragingEvent; import com.android.systemui.stackdivider.events.StoppedDragingEvent; @@ -75,8 +76,8 @@ public class ForcedResizableInfoActivityController { public ForcedResizableInfoActivityController(Context context) { mContext = context; EventBus.getDefault().register(this); - SystemServicesProxy.getInstance(context).registerTaskStackListener( - new TaskStackListener() { + ActivityManagerWrapper.getInstance().registerTaskStackListener( + new SysUiTaskStackChangeListener() { @Override public void onActivityForcedResizable(String packageName, int taskId, int reason) { diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java index c24512649c2f..85a60624c275 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java @@ -16,7 +16,6 @@ package com.android.systemui.stackdivider; -import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.view.WindowManager.DOCKED_INVALID; import android.app.ActivityManager; @@ -56,7 +55,7 @@ public class WindowManagerProxy { private final Rect mTouchableRegion = new Rect(); private boolean mDimLayerVisible; - private int mDimLayerTargetStack; + private int mDimLayerTargetWindowingMode; private float mDimLayerAlpha; private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); @@ -88,8 +87,7 @@ public class WindowManagerProxy { @Override public void run() { try { - ActivityManager.getService().moveTasksToFullscreenStack( - DOCKED_STACK_ID, false /* onTop */); + ActivityManager.getService().dismissSplitScreenMode(false /* onTop */); } catch (RemoteException e) { Log.w(TAG, "Failed to remove stack: " + e); } @@ -100,8 +98,7 @@ public class WindowManagerProxy { @Override public void run() { try { - ActivityManager.getService().resizeStack( - DOCKED_STACK_ID, null, true, true, false, -1); + ActivityManager.getService().dismissSplitScreenMode(true /* onTop */); } catch (RemoteException e) { Log.w(TAG, "Failed to resize stack: " + e); } @@ -113,18 +110,7 @@ public class WindowManagerProxy { public void run() { try { WindowManagerGlobal.getWindowManagerService().setResizeDimLayer(mDimLayerVisible, - mDimLayerTargetStack, mDimLayerAlpha); - } catch (RemoteException e) { - Log.w(TAG, "Failed to resize stack: " + e); - } - } - }; - - private final Runnable mSwapRunnable = new Runnable() { - @Override - public void run() { - try { - ActivityManager.getService().swapDockedAndFullscreenStack(); + mDimLayerTargetWindowingMode, mDimLayerAlpha); } catch (RemoteException e) { Log.w(TAG, "Failed to resize stack: " + e); } @@ -211,17 +197,13 @@ public class WindowManagerProxy { return DOCKED_INVALID; } - public void setResizeDimLayer(boolean visible, int targetStackId, float alpha) { + public void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) { mDimLayerVisible = visible; - mDimLayerTargetStack = targetStackId; + mDimLayerTargetWindowingMode = targetWindowingMode; mDimLayerAlpha = alpha; mExecutor.execute(mDimLayerRunnable); } - public void swapTasks() { - mExecutor.execute(mSwapRunnable); - } - public void setTouchRegion(Rect region) { synchronized (mDockedRect) { mTouchableRegion.set(region); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index 68fe9a83c707..eb5619b11487 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -29,7 +29,6 @@ import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewAnimationUtils; -import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityManager; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; @@ -123,7 +122,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private Interpolator mCurrentAppearInterpolator; private Interpolator mCurrentAlphaInterpolator; - private NotificationBackgroundView mBackgroundNormal; + protected NotificationBackgroundView mBackgroundNormal; private NotificationBackgroundView mBackgroundDimmed; private ObjectAnimator mBackgroundAnimator; private RectF mAppearAnimationRect = new RectF(); @@ -173,12 +172,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private int mOverrideTint; private float mOverrideAmount; private boolean mShadowHidden; - private boolean mWasActivatedOnDown; /** * Similar to mDimmed but is also true if it's not dimmable but should be */ private boolean mNeedsDimming; private int mDimmedAlpha; + private boolean mBlockNextTouch; public ActivatableNotificationView(Context context, AttributeSet attrs) { super(context, attrs); @@ -204,7 +203,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } else { makeInactive(true /* animate */); } - }, this::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap); + }, super::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap); } @Override @@ -241,9 +240,15 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - if (mNeedsDimming && !mActivated && ev.getActionMasked() == MotionEvent.ACTION_DOWN + if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN && disallowSingleClick(ev) && !isTouchExplorationEnabled()) { - return true; + if (!mActivated) { + return true; + } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) { + mBlockNextTouch = true; + makeInactive(true /* animate */); + return true; + } } return super.onInterceptTouchEvent(ev); } @@ -263,10 +268,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public boolean onTouchEvent(MotionEvent event) { boolean result; - if (event.getAction() == MotionEvent.ACTION_DOWN) { - mWasActivatedOnDown = mActivated; + if (mBlockNextTouch) { + mBlockNextTouch = false; + return false; } - if ((mNeedsDimming && !mActivated) && !isTouchExplorationEnabled() && isInteractive()) { + if (mNeedsDimming && !isTouchExplorationEnabled() && isInteractive()) { boolean wasActivated = mActivated; result = handleTouchEventDimmed(event); if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) { @@ -312,7 +318,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public boolean performClick() { - if (mWasActivatedOnDown || !mNeedsDimming || isTouchExplorationEnabled()) { + if (!mNeedsDimming || isTouchExplorationEnabled()) { return super.performClick(); } return false; @@ -689,6 +695,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundNormal.getVisibility() == View.VISIBLE ? 1.0f : 0.0f); } + protected void updateBackgroundClipping() { + mBackgroundNormal.setBottomAmountClips(!isChildInGroup()); + mBackgroundDimmed.setBottomAmountClips(!isChildInGroup()); + } + protected boolean shouldHideBackground() { return mDark; } @@ -895,12 +906,38 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView contentView.setAlpha(contentAlpha); } + @Override + protected void applyRoundness() { + super.applyRoundness(); + applyBackgroundRoundness(getCurrentBackgroundRadiusTop(), + getCurrentBackgroundRadiusBottom()); + } + + protected void applyBackgroundRoundness(float topRadius, float bottomRadius) { + mBackgroundDimmed.setRoundness(topRadius, bottomRadius); + mBackgroundNormal.setRoundness(topRadius, bottomRadius); + } + + @Override + protected void setBackgroundTop(int backgroundTop) { + mBackgroundDimmed.setBackgroundTop(backgroundTop); + mBackgroundNormal.setBackgroundTop(backgroundTop); + } + protected abstract View getContentView(); public int calculateBgColor() { return calculateBgColor(true /* withTint */, true /* withOverRide */); } + @Override + protected boolean childNeedsClipping(View child) { + if (child instanceof NotificationBackgroundView && isClippingNeeded()) { + return true; + } + return super.childNeedsClipping(child); + } + /** * @param withTint should a possible tint be factored in? * @param withOverRide should the value be interpolated with {@link #mOverrideTint} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 634927503747..fa177f2bb26c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -18,11 +18,13 @@ package com.android.systemui.statusbar; import android.content.ComponentName; import android.graphics.Rect; +import android.hardware.fingerprint.IFingerprintDialogReceiver; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; import android.support.annotation.VisibleForTesting; import android.util.Pair; @@ -82,6 +84,15 @@ public class CommandQueue extends IStatusBar.Stub { private static final int MSG_TOGGLE_PANEL = 35 << MSG_SHIFT; private static final int MSG_SHOW_SHUTDOWN_UI = 36 << MSG_SHIFT; private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR = 37 << MSG_SHIFT; + private static final int MSG_ROTATION_PROPOSAL = 38 << MSG_SHIFT; + private static final int MSG_FINGERPRINT_SHOW = 39 << MSG_SHIFT; + private static final int MSG_FINGERPRINT_AUTHENTICATED = 40 << MSG_SHIFT; + private static final int MSG_FINGERPRINT_HELP = 41 << MSG_SHIFT; + private static final int MSG_FINGERPRINT_ERROR = 42 << MSG_SHIFT; + private static final int MSG_FINGERPRINT_HIDE = 43 << MSG_SHIFT; + private static final int MSG_SHOW_CHARGING_ANIMATION = 44 << MSG_SHIFT; + private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT; + private static final int MSG_SHOW_PINNING_TOAST_ESCAPE = 46 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -115,7 +126,7 @@ public class CommandQueue extends IStatusBar.Stub { default void topAppWindowChanged(boolean visible) { } default void setImeWindowStatus(IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { } - default void showRecentApps(boolean triggeredFromAltTab, boolean fromHome) { } + default void showRecentApps(boolean triggeredFromAltTab) { } default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { } default void toggleRecentApps() { } default void toggleSplitScreen() { } @@ -140,8 +151,20 @@ public class CommandQueue extends IStatusBar.Stub { default void clickTile(ComponentName tile) { } default void handleSystemKey(int arg1) { } + default void showPinningEnterExitToast(boolean entering) { } + default void showPinningEscapeToast() { } default void handleShowGlobalActionsMenu() { } default void handleShowShutdownUi(boolean isReboot, String reason) { } + + default void showWirelessChargingAnimation(int batteryLevel) { } + + default void onRotationProposal(int rotation, boolean isValid) { } + + default void showFingerprintDialog(Bundle bundle, IFingerprintDialogReceiver receiver) { } + default void onFingerprintAuthenticated() { } + default void onFingerprintHelp(String message) { } + default void onFingerprintError(String error) { } + default void hideFingerprintDialog() { } } @VisibleForTesting @@ -265,11 +288,11 @@ public class CommandQueue extends IStatusBar.Stub { } } - public void showRecentApps(boolean triggeredFromAltTab, boolean fromHome) { + public void showRecentApps(boolean triggeredFromAltTab) { synchronized (mLock) { mHandler.removeMessages(MSG_SHOW_RECENT_APPS); - mHandler.obtainMessage(MSG_SHOW_RECENT_APPS, - triggeredFromAltTab ? 1 : 0, fromHome ? 1 : 0, null).sendToTarget(); + mHandler.obtainMessage(MSG_SHOW_RECENT_APPS, triggeredFromAltTab ? 1 : 0, 0, + null).sendToTarget(); } } @@ -435,6 +458,21 @@ public class CommandQueue extends IStatusBar.Stub { } @Override + public void showPinningEnterExitToast(boolean entering) { + synchronized (mLock) { + mHandler.obtainMessage(MSG_SHOW_PINNING_TOAST_ENTER_EXIT, entering).sendToTarget(); + } + } + + @Override + public void showPinningEscapeToast() { + synchronized (mLock) { + mHandler.obtainMessage(MSG_SHOW_PINNING_TOAST_ESCAPE).sendToTarget(); + } + } + + + @Override public void showGlobalActionsMenu() { synchronized (mLock) { mHandler.removeMessages(MSG_SHOW_GLOBAL_ACTIONS); @@ -458,6 +496,61 @@ public class CommandQueue extends IStatusBar.Stub { } } + @Override + public void showWirelessChargingAnimation(int batteryLevel) { + mHandler.removeMessages(MSG_SHOW_CHARGING_ANIMATION); + mHandler.obtainMessage(MSG_SHOW_CHARGING_ANIMATION, batteryLevel, 0) + .sendToTarget(); + } + + @Override + public void onProposedRotationChanged(int rotation, boolean isValid) { + synchronized (mLock) { + mHandler.removeMessages(MSG_ROTATION_PROPOSAL); + mHandler.obtainMessage(MSG_ROTATION_PROPOSAL, rotation, isValid ? 1 : 0, + null).sendToTarget(); + } + } + + @Override + public void showFingerprintDialog(Bundle bundle, IFingerprintDialogReceiver receiver) { + synchronized (mLock) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = bundle; + args.arg2 = receiver; + mHandler.obtainMessage(MSG_FINGERPRINT_SHOW, args) + .sendToTarget(); + } + } + + @Override + public void onFingerprintAuthenticated() { + synchronized (mLock) { + mHandler.obtainMessage(MSG_FINGERPRINT_AUTHENTICATED).sendToTarget(); + } + } + + @Override + public void onFingerprintHelp(String message) { + synchronized (mLock) { + mHandler.obtainMessage(MSG_FINGERPRINT_HELP, message).sendToTarget(); + } + } + + @Override + public void onFingerprintError(String error) { + synchronized (mLock) { + mHandler.obtainMessage(MSG_FINGERPRINT_ERROR, error).sendToTarget(); + } + } + + @Override + public void hideFingerprintDialog() { + synchronized (mLock) { + mHandler.obtainMessage(MSG_FINGERPRINT_HIDE).sendToTarget(); + } + } + private final class H extends Handler { private H(Looper l) { super(l); @@ -529,7 +622,7 @@ public class CommandQueue extends IStatusBar.Stub { break; case MSG_SHOW_RECENT_APPS: for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).showRecentApps(msg.arg1 != 0, msg.arg2 != 0); + mCallbacks.get(i).showRecentApps(msg.arg1 != 0); } break; case MSG_HIDE_RECENT_APPS: @@ -654,6 +747,56 @@ public class CommandQueue extends IStatusBar.Stub { mCallbacks.get(i).setTopAppHidesStatusBar(msg.arg1 != 0); } break; + case MSG_ROTATION_PROPOSAL: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).onRotationProposal(msg.arg1, msg.arg2 != 0); + } + break; + case MSG_FINGERPRINT_SHOW: + mHandler.removeMessages(MSG_FINGERPRINT_ERROR); + mHandler.removeMessages(MSG_FINGERPRINT_HELP); + mHandler.removeMessages(MSG_FINGERPRINT_AUTHENTICATED); + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).showFingerprintDialog( + (Bundle)((SomeArgs)msg.obj).arg1, + (IFingerprintDialogReceiver)((SomeArgs)msg.obj).arg2); + } + break; + case MSG_FINGERPRINT_AUTHENTICATED: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).onFingerprintAuthenticated(); + } + break; + case MSG_FINGERPRINT_HELP: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).onFingerprintHelp((String) msg.obj); + } + break; + case MSG_FINGERPRINT_ERROR: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).onFingerprintError((String) msg.obj); + } + break; + case MSG_FINGERPRINT_HIDE: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).hideFingerprintDialog(); + } + break; + case MSG_SHOW_CHARGING_ANIMATION: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).showWirelessChargingAnimation(msg.arg1); + } + break; + case MSG_SHOW_PINNING_TOAST_ENTER_EXIT: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).showPinningEnterExitToast((Boolean) msg.obj); + } + break; + case MSG_SHOW_PINNING_TOAST_ESCAPE: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).showPinningEscapeToast(); + } + break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 966e78997244..b3f68d357083 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar; +import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback; import android.animation.Animator; @@ -26,16 +27,20 @@ import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.content.res.Configuration; +import android.graphics.Path; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.FloatProperty; +import android.util.MathUtils; import android.util.Property; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.NotificationHeaderView; @@ -64,14 +69,16 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.statusbar.NotificationGuts.GutsContent; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; -import com.android.systemui.statusbar.notification.AboveShelfObserver; +import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.HybridNotificationView; import com.android.systemui.statusbar.notification.NotificationInflater; import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.notification.NotificationViewWrapper; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.stack.AmbientState; import com.android.systemui.statusbar.stack.AnimationProperties; import com.android.systemui.statusbar.stack.ExpandableViewState; import com.android.systemui.statusbar.stack.NotificationChildrenContainer; @@ -100,7 +107,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private int mIconTransformContentShift; private int mIconTransformContentShiftNoIcon; private int mNotificationMinHeightLegacy; + private int mNotificationMinHeightBeforeP; private int mMaxHeadsUpHeightLegacy; + private int mMaxHeadsUpHeightBeforeP; private int mMaxHeadsUpHeight; private int mMaxHeadsUpHeightIncreased; private int mNotificationMinHeight; @@ -108,6 +117,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private int mNotificationMaxHeight; private int mNotificationAmbientHeight; private int mIncreasedPaddingBetweenElements; + private int mNotificationLaunchHeight; + private boolean mMustStayOnScreen; /** Does this row contain layouts that can adapt to row expansion */ private boolean mExpandable; @@ -166,14 +177,21 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mIsSystemChildExpanded; private boolean mIsPinned; private FalsingManager mFalsingManager; + private boolean mExpandAnimationRunning; private AboveShelfChangedListener mAboveShelfChangedListener; private HeadsUpManager mHeadsUpManager; + private View mHelperButton; private boolean mJustClicked; private boolean mIconAnimationRunning; private boolean mShowNoBackground; private ExpandableNotificationRow mNotificationParent; private OnExpandClickListener mOnExpandClickListener; + + // Listener will be called when receiving a long click event. + // Use #setLongPressPosition to optionally assign positional data with the long press. + private LongPressListener mLongPressListener; + private boolean mGroupExpansionChanging; /** @@ -196,7 +214,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private OnClickListener mExpandClickListener = new OnClickListener() { @Override public void onClick(View v) { - if (!mShowingPublic && (!mIsLowPriority || isExpanded()) + if (!shouldShowPublic() && (!mIsLowPriority || isExpanded()) && mGroupManager.isSummaryOfGroup(mStatusBarNotification)) { mGroupExpansionChanging = true; final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification); @@ -258,6 +276,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private float mTranslationWhenRemoved; private boolean mWasChildInGroupWhenRemoved; private int mNotificationColorAmbient; + private NotificationViewState mNotificationViewState; @Override public boolean isGroupExpansionChanging() { @@ -351,6 +370,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mNotificationInflater.inflateNotificationViews(); } + @Override + public void setPressed(boolean pressed) { + if (isOnKeyguard() || mEntry.notification.getNotification().contentIntent == null) { + // We're dropping the ripple if we have a collapse / launch animation + super.setPressed(pressed); + } + } + public void onNotificationUpdated() { for (NotificationContentView l : mLayouts) { l.onNotificationUpdated(mEntry); @@ -377,6 +404,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView updateLimits(); updateIconVisibilities(); updateShelfIconColor(); + + showBlockingHelper(mEntry.userSentiment == + NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE); } @VisibleForTesting @@ -428,9 +458,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView boolean customView = layout.getContractedChild().getId() != com.android.internal.R.id.status_bar_latest_event_content; boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N; + boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P; int minHeight; - if (customView && beforeN && !mIsSummaryWithChildren) { - minHeight = mNotificationMinHeightLegacy; + if (customView && beforeP && !mIsSummaryWithChildren) { + minHeight = beforeN ? mNotificationMinHeightLegacy : mNotificationMinHeightBeforeP; } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) { minHeight = mNotificationMinHeightLarge; } else { @@ -440,13 +471,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView layout.getHeadsUpChild().getId() != com.android.internal.R.id.status_bar_latest_event_content; int headsUpheight; - if (headsUpCustom && beforeN) { - headsUpheight = mMaxHeadsUpHeightLegacy; + if (headsUpCustom && beforeP) { + headsUpheight = beforeN ? mMaxHeadsUpHeightLegacy : mMaxHeadsUpHeightBeforeP; } else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) { headsUpheight = mMaxHeadsUpHeightIncreased; } else { headsUpheight = mMaxHeadsUpHeight; } + NotificationViewWrapper headsUpWrapper = layout.getVisibleWrapper( + NotificationContentView.VISIBLE_TYPE_HEADSUP); + if (headsUpWrapper != null) { + headsUpheight = Math.max(headsUpheight, headsUpWrapper.getMinLayoutHeight()); + } layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight, mNotificationAmbientHeight); } @@ -476,6 +512,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView notifyHeightChanged(false /* needsAnimation */); } if (isHeadsUp) { + mMustStayOnScreen = true; setAboveShelf(true); } else if (isAboveShelf() != wasAboveShelf) { mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); @@ -502,6 +539,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView addChildNotification(row, -1); } + @Override + public void setHeadsUpIsVisible() { + super.setHeadsUpIsVisible(); + mMustStayOnScreen = false; + } + /** * Add a child notification to this view. * @@ -523,6 +566,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } onChildrenCountChanged(); row.setIsChildInGroup(false, null); + row.setBottomRoundness(0.0f, false /* animate */); } @Override @@ -551,6 +595,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mNotificationParent.updateBackgroundForGroupState(); } updateIconVisibilities(); + updateBackgroundClipping(); } @Override @@ -765,7 +810,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * {@link #getNotificationHeader()} in case it is a low-priority group. */ public NotificationHeaderView getVisibleNotificationHeader() { - if (mIsSummaryWithChildren && !mShowingPublic) { + if (mIsSummaryWithChildren && !shouldShowPublic()) { return mChildrenContainer.getVisibleHeader(); } return getShowingLayout().getVisibleNotificationHeader(); @@ -788,6 +833,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mOnExpandClickListener = onExpandClickListener; } + public void setLongPressListener(LongPressListener longPressListener) { + mLongPressListener = longPressListener; + } + @Override public void setOnClickListener(@Nullable OnClickListener l) { super.setOnClickListener(l); @@ -900,6 +949,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView addView(mMenuRow.getMenuView(), menuIndex); } for (NotificationContentView l : mLayouts) { + l.initView(); l.reInflateViews(); } mNotificationInflater.onDensityOrFontScaleChanged(); @@ -1009,6 +1059,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mKeepInParent = keepInParent; } + @Override public boolean isRemoved() { return mRemoved; } @@ -1060,9 +1111,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mGroupParentWhenDismissed; } - public void performDismiss() { - if (mOnDismissRunnable != null) { - mOnDismissRunnable.run(); + public void performDismiss(boolean fromAccessibility) { + if (mGroupManager.isOnlyChildInGroup(getStatusBarNotification())) { + ExpandableNotificationRow groupSummary = + mGroupManager.getLogicalGroupSummary(getStatusBarNotification()); + if (groupSummary.isClearable()) { + groupSummary.performDismiss(fromAccessibility); + } + } + setDismissed(true, fromAccessibility); + if (isClearable()) { + if (mOnDismissRunnable != null) { + mOnDismissRunnable.run(); + } } } @@ -1123,6 +1184,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } private void updateContentTransformation() { + if (mExpandAnimationRunning) { + return; + } float contentAlpha; float translationY = -mContentTransformationAmount * mIconTransformContentShift; if (mIsLastChild) { @@ -1246,16 +1310,25 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } private void initDimens() { - mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy); - mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height); - mNotificationMinHeightLarge = getFontScaledHeight( + mNotificationMinHeightLegacy = NotificationUtils.getFontScaledHeight(mContext, + R.dimen.notification_min_height_legacy); + mNotificationMinHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext, + R.dimen.notification_min_height_before_p); + mNotificationMinHeight = NotificationUtils.getFontScaledHeight(mContext, + R.dimen.notification_min_height); + mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_min_height_increased); - mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height); - mNotificationAmbientHeight = getFontScaledHeight(R.dimen.notification_ambient_height); - mMaxHeadsUpHeightLegacy = getFontScaledHeight( + mNotificationMaxHeight = NotificationUtils.getFontScaledHeight(mContext, + R.dimen.notification_max_height); + mNotificationAmbientHeight = NotificationUtils.getFontScaledHeight(mContext, + R.dimen.notification_ambient_height); + mMaxHeadsUpHeightLegacy = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_max_heads_up_height_legacy); - mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height); - mMaxHeadsUpHeightIncreased = getFontScaledHeight( + mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext, + R.dimen.notification_max_heads_up_height_before_p); + mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext, + R.dimen.notification_max_heads_up_height); + mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_max_heads_up_height_increased); Resources res = getResources(); @@ -1270,17 +1343,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** - * @param dimenId the dimen to look up - * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp - */ - private int getFontScaledHeight(int dimenId) { - int dimensionPixelSize = getResources().getDimensionPixelSize(dimenId); - float factor = Math.max(1.0f, getResources().getDisplayMetrics().scaledDensity / - getResources().getDisplayMetrics().density); - return (int) (dimensionPixelSize * factor); - } - - /** * Resets this view so it can be re-used for an updated notification. */ public void reset() { @@ -1289,6 +1351,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView requestLayout(); } + public void showBlockingHelper(boolean show) { + mHelperButton.setVisibility(show ? View.VISIBLE : View.GONE); + } + @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -1296,6 +1362,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded); mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout}; + final NotificationGutsManager gutsMan = Dependency.get(NotificationGutsManager.class); + mHelperButton = findViewById(R.id.helper); + mHelperButton.setOnClickListener(view -> { + doLongClickCallback(); + }); + for (NotificationContentView l : mLayouts) { l.setExpandClickListener(mExpandClickListener); l.setContainingNotification(this); @@ -1338,6 +1410,47 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } + private void doLongClickCallback() { + doLongClickCallback(getWidth() / 2, getHeight() / 2); + } + + public void doLongClickCallback(int x, int y) { + createMenu(); + MenuItem menuItem = getProvider().getLongpressMenuItem(mContext); + if (mLongPressListener != null && menuItem != null) { + mLongPressListener.onLongPress(this, x, y, menuItem); + } + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (KeyEvent.isConfirmKey(keyCode)) { + event.startTracking(); + return true; + } + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (KeyEvent.isConfirmKey(keyCode)) { + if (!event.isCanceled()) { + performClick(); + } + return true; + } + return super.onKeyUp(keyCode, event); + } + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + if (KeyEvent.isConfirmKey(keyCode)) { + doLongClickCallback(); + return true; + } + return false; + } + public void resetTranslation() { if (mTranslateAnim != null) { mTranslateAnim.cancel(); @@ -1355,6 +1468,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mMenuRow.resetMenu(); } + public CharSequence getActiveRemoteInputText() { + return mPrivateLayout.getActiveRemoteInputText(); + } + public void animateTranslateNotification(final float leftTarget) { if (mTranslateAnim != null) { mTranslateAnim.cancel(); @@ -1442,10 +1559,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } private void updateChildrenVisibility() { - mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren ? VISIBLE - : INVISIBLE); + boolean hideContentWhileLaunching = mExpandAnimationRunning && mGuts != null + && mGuts.isExposed(); + mPrivateLayout.setVisibility(!shouldShowPublic() && !mIsSummaryWithChildren + && !hideContentWhileLaunching ? VISIBLE : INVISIBLE); if (mChildrenContainer != null) { - mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE + mChildrenContainer.setVisibility(!shouldShowPublic() && mIsSummaryWithChildren + && !hideContentWhileLaunching ? VISIBLE : INVISIBLE); } // The limits might have changed if the view suddenly became a group or vice versa @@ -1484,6 +1604,62 @@ public class ExpandableNotificationRow extends ActivatableNotificationView updateShelfIconColor(); } + public void applyExpandAnimationParams(ExpandAnimationParameters params) { + if (params == null) { + return; + } + setTranslationY(params.getTop()); + float zProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( + params.getProgress(0, 50)); + float translationZ = MathUtils.lerp(params.getStartTranslationZ(), + mNotificationLaunchHeight, + zProgress); + setTranslationZ(translationZ); + setActualHeight(params.getHeight()); + mBackgroundNormal.setExpandAnimationParams(params); + } + + public void setExpandAnimationRunning(boolean expandAnimationRunning) { + if (expandAnimationRunning) { + View contentView; + if (mIsSummaryWithChildren) { + contentView = mChildrenContainer; + } else { + contentView = getShowingLayout(); + } + if (mGuts != null && mGuts.isExposed()) { + contentView = mGuts; + } + contentView.animate() + .alpha(0f) + .setDuration(ActivityLaunchAnimator.ANIMATION_DURATION_FADE_CONTENT) + .setInterpolator(Interpolators.ALPHA_OUT); + setAboveShelf(true); + mExpandAnimationRunning = true; + mNotificationViewState.cancelAnimations(this); + mNotificationLaunchHeight = AmbientState.getNotificationLaunchHeight(getContext()); + } else { + mExpandAnimationRunning = false; + setAboveShelf(isAboveShelf()); + if (mGuts != null) { + mGuts.setAlpha(1.0f); + } + } + updateChildrenVisibility(); + updateClipping(); + mBackgroundNormal.setExpandAnimationRunning(expandAnimationRunning); + } + + @Override + protected boolean shouldClipToActualHeight() { + return super.shouldClipToActualHeight() && !mExpandAnimationRunning; + } + + @Override + public boolean isExpandAnimationRunning() { + return mExpandAnimationRunning; + } + /** * Tap sounds should not be played when we're unlocking. * Doing so would cause audio collision and the system would feel unpolished. @@ -1496,7 +1672,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public boolean isExpandable() { - if (mIsSummaryWithChildren && !mShowingPublic) { + if (mIsSummaryWithChildren && !shouldShowPublic()) { return !mChildrenExpanded; } return mEnableNonGroupedNotificationExpand && mExpandable; @@ -1541,7 +1717,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { mFalsingManager.setNotificationExpanded(); - if (mIsSummaryWithChildren && !mShowingPublic && allowChildExpansion + if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion && !mChildrenContainer.showingAsLowPriority()) { final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification); mGroupManager.setGroupExpanded(mStatusBarNotification, userExpanded); @@ -1701,6 +1877,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPrivateLayout.updateExpandButtons(isExpandable()); updateChildrenHeaderAppearance(); updateChildrenVisibility(); + applyChildrenRoundness(); } public void updateChildrenHeaderAppearance() { @@ -1835,7 +2012,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE); updateChildrenVisibility(); } else { - animateShowingPublic(delay, duration); + animateShowingPublic(delay, duration, mShowingPublic); } NotificationContentView showingLayout = getShowingLayout(); showingLayout.updateBackgroundColor(animated); @@ -1845,13 +2022,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mShowingPublicInitialized = true; } - private void animateShowingPublic(long delay, long duration) { + private void animateShowingPublic(long delay, long duration, boolean showingPublic) { View[] privateViews = mIsSummaryWithChildren ? new View[] {mChildrenContainer} : new View[] {mPrivateLayout}; View[] publicViews = new View[] {mPublicLayout}; - View[] hiddenChildren = mShowingPublic ? privateViews : publicViews; - View[] shownChildren = mShowingPublic ? publicViews : privateViews; + View[] hiddenChildren = showingPublic ? privateViews : publicViews; + View[] shownChildren = showingPublic ? publicViews : privateViews; for (final View hiddenView : hiddenChildren) { hiddenView.setVisibility(View.VISIBLE); hiddenView.animate().cancel(); @@ -1879,7 +2056,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public boolean mustStayOnScreen() { - return mIsHeadsUp; + return mIsHeadsUp && mMustStayOnScreen; } /** @@ -1888,7 +2065,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * see {@link #isClearable()}. */ public boolean canViewBeDismissed() { - return isClearable() && (!mShowingPublic || !mSensitiveHiddenInGeneral); + return isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); + } + + private boolean shouldShowPublic() { + return mSensitive && mHideSensitiveForIntrinsicHeight; } public void makeActionsVisibile() { @@ -1934,7 +2115,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public boolean isContentExpandable() { - if (mIsSummaryWithChildren && !mShowingPublic) { + if (mIsSummaryWithChildren && !shouldShowPublic()) { return true; } NotificationContentView showingLayout = getShowingLayout(); @@ -1943,7 +2124,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override protected View getContentView() { - if (mIsSummaryWithChildren && !mShowingPublic) { + if (mIsSummaryWithChildren && !shouldShowPublic()) { return mChildrenContainer; } return getShowingLayout(); @@ -2008,7 +2189,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public int getMaxContentHeight() { - if (mIsSummaryWithChildren && !mShowingPublic) { + if (mIsSummaryWithChildren && !shouldShowPublic()) { return mChildrenContainer.getMaxContentHeight(); } NotificationContentView showingLayout = getShowingLayout(); @@ -2022,7 +2203,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) { return getPinnedHeadsUpHeight(false /* atLeastMinHeight */); - } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) { + } else if (mIsSummaryWithChildren && !isGroupExpanded() && !shouldShowPublic()) { return mChildrenContainer.getMinHeight(); } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp) { return mHeadsUpHeight; @@ -2033,7 +2214,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public int getCollapsedHeight() { - if (mIsSummaryWithChildren && !mShowingPublic) { + if (mIsSummaryWithChildren && !shouldShowPublic()) { return mChildrenContainer.getCollapsedHeight(); } return getMinHeight(); @@ -2052,6 +2233,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void setClipBottomAmount(int clipBottomAmount) { + if (mExpandAnimationRunning) { + return; + } if (clipBottomAmount != mClipBottomAmount) { super.setClipBottomAmount(clipBottomAmount); for (NotificationContentView l : mLayouts) { @@ -2073,7 +2257,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public NotificationContentView getShowingLayout() { - return mShowingPublic ? mPublicLayout : mPrivateLayout; + return shouldShowPublic() ? mPublicLayout : mPrivateLayout; } public void setLegacy(boolean legacy) { @@ -2179,7 +2363,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (header != null && header.isInTouchRect(x - getTranslation(), y)) { return true; } - if ((!mIsSummaryWithChildren || mShowingPublic) + if ((!mIsSummaryWithChildren || shouldShowPublic()) && getShowingLayout().disallowSingleClick(x, y)) { return true; } @@ -2205,10 +2389,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); if (canViewBeDismissed()) { info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); } - boolean expandable = mShowingPublic; + boolean expandable = shouldShowPublic(); boolean isExpanded = false; if (!expandable) { if (mIsSummaryWithChildren) { @@ -2237,13 +2422,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } switch (action) { case AccessibilityNodeInfo.ACTION_DISMISS: - NotificationStackScrollLayout.performDismiss(this, mGroupManager, - true /* fromAccessibility */); + performDismiss(true /* fromAccessibility */); return true; case AccessibilityNodeInfo.ACTION_COLLAPSE: case AccessibilityNodeInfo.ACTION_EXPAND: mExpandClickListener.onClick(this); return true; + case AccessibilityNodeInfo.ACTION_LONG_CLICK: + doLongClickCallback(); + return true; } return false; } @@ -2258,13 +2445,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public ExpandableViewState createNewViewState(StackScrollState stackScrollState) { - return new NotificationViewState(stackScrollState); + mNotificationViewState = new NotificationViewState(stackScrollState); + return mNotificationViewState; } @Override public boolean isAboveShelf() { return !isOnKeyguard() - && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf)); + && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf) + || mExpandAnimationRunning); } public void setShowAmbient(boolean showAmbient) { @@ -2277,6 +2466,56 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } + @Override + protected boolean childNeedsClipping(View child) { + if (child instanceof NotificationContentView) { + NotificationContentView contentView = (NotificationContentView) child; + if (isClippingNeeded()) { + return true; + } else if (!hasNoRounding() + && contentView.shouldClipToRounding(getCurrentTopRoundness() != 0.0f, + getCurrentBottomRoundness() != 0.0f)) { + return true; + } + } else if (child == mChildrenContainer) { + if (isClippingNeeded() || !hasNoRounding()) { + return true; + } + } else if (child instanceof NotificationGuts) { + return !hasNoRounding(); + } + return super.childNeedsClipping(child); + } + + @Override + protected void applyRoundness() { + super.applyRoundness(); + applyChildrenRoundness(); + } + + private void applyChildrenRoundness() { + if (mIsSummaryWithChildren) { + mChildrenContainer.setCurrentBottomRoundness(getCurrentBottomRoundness()); + } + } + + @Override + public Path getCustomClipPath(View child) { + if (child instanceof NotificationGuts) { + return getClipPath(true, /* ignoreTranslation */ + false /* clipRoundedToBottom */); + } + if (child instanceof NotificationChildrenContainer) { + return getClipPath(false, /* ignoreTranslation */ + true /* clipRoundedToBottom */); + } + return super.getCustomClipPath(child); + } + + private boolean hasNoRounding() { + return getCurrentBottomRoundness() == 0.0f && getCurrentTopRoundness() == 0.0f; + } + public boolean isShowingAmbient() { return mShowAmbient; } @@ -2300,9 +2539,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void applyToView(View view) { - super.applyToView(view); if (view instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (row.isExpandAnimationRunning()) { + return; + } + super.applyToView(view); row.applyChildrenState(mOverallState); } } @@ -2320,9 +2562,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void animateTo(View child, AnimationProperties properties) { - super.animateTo(child, properties); if (child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (row.isExpandAnimationRunning()) { + return; + } + super.animateTo(child, properties); row.startChildAnimation(mOverallState, properties); } } @@ -2332,4 +2577,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) { mChildrenContainer = childrenContainer; } + + /** + * Equivalent to View.OnLongClickListener with coordinates + */ + public interface LongPressListener { + /** + * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates + * @return whether the longpress was handled + */ + boolean onLongPress(View v, int x, int y, MenuItem item); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java index 255689072b12..66b3a75f4037 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java @@ -18,88 +18,344 @@ package com.android.systemui.statusbar; import android.content.Context; import android.content.res.Resources; +import android.graphics.Canvas; import android.graphics.Outline; +import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; import android.view.ViewOutlineProvider; + +import com.android.settingslib.Utils; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.AnimatableProperty; +import com.android.systemui.statusbar.notification.PropertyAnimator; +import com.android.systemui.statusbar.stack.AnimationProperties; +import com.android.systemui.statusbar.stack.StackStateAnimator; /** * Like {@link ExpandableView}, but setting an outline for the height and clipping. */ public abstract class ExpandableOutlineView extends ExpandableView { + private static final AnimatableProperty TOP_ROUNDNESS = AnimatableProperty.from( + "topRoundness", + ExpandableOutlineView::setTopRoundnessInternal, + ExpandableOutlineView::getCurrentTopRoundness, + R.id.top_roundess_animator_tag, + R.id.top_roundess_animator_end_tag, + R.id.top_roundess_animator_start_tag); + private static final AnimatableProperty BOTTOM_ROUNDNESS = AnimatableProperty.from( + "bottomRoundness", + ExpandableOutlineView::setBottomRoundnessInternal, + ExpandableOutlineView::getCurrentBottomRoundness, + R.id.bottom_roundess_animator_tag, + R.id.bottom_roundess_animator_end_tag, + R.id.bottom_roundess_animator_start_tag); + private static final AnimationProperties ROUNDNESS_PROPERTIES = + new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); + private static final Path EMPTY_PATH = new Path(); + private final Rect mOutlineRect = new Rect(); + private final Path mClipPath = new Path(); private boolean mCustomOutline; private float mOutlineAlpha = -1f; private float mOutlineRadius; + private boolean mAlwaysRoundBothCorners; + private Path mTmpPath = new Path(); + private Path mTmpPath2 = new Path(); + private float mCurrentBottomRoundness; + private float mCurrentTopRoundness; + private float mBottomRoundness; + private float mTopRoundness; + private int mBackgroundTop; /** * {@code true} if the children views of the {@link ExpandableOutlineView} are translated when * it is moved. Otherwise, the translation is set on the {@code ExpandableOutlineView} itself. */ protected boolean mShouldTranslateContents; + private boolean mClipRoundedToClipTopAmount; + private float mDistanceToTopRoundness = -1; private final ViewOutlineProvider mProvider = new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { - int translation = mShouldTranslateContents ? (int) getTranslation() : 0; - if (!mCustomOutline) { - outline.setRoundRect(translation, - mClipTopAmount, - getWidth() + translation, - Math.max(getActualHeight() - mClipBottomAmount, mClipTopAmount), - mOutlineRadius); + if (!mCustomOutline && mCurrentTopRoundness == 0.0f + && mCurrentBottomRoundness == 0.0f && !mAlwaysRoundBothCorners) { + int translation = mShouldTranslateContents ? (int) getTranslation() : 0; + int left = Math.max(translation, 0); + int top = mClipTopAmount + mBackgroundTop; + int right = getWidth() + Math.min(translation, 0); + int bottom = Math.max(getActualHeight() - mClipBottomAmount, top); + outline.setRect(left, top, right, bottom); } else { - outline.setRoundRect(mOutlineRect, mOutlineRadius); + Path clipPath = getClipPath(); + if (clipPath != null && clipPath.isConvex()) { + // The path might not be convex in border cases where the view is small and + // clipped + outline.setConvexPath(clipPath); + } } outline.setAlpha(mOutlineAlpha); } }; + private Path getClipPath() { + return getClipPath(false, /* ignoreTranslation */ + false /* clipRoundedToBottom */); + } + + protected Path getClipPath(boolean ignoreTranslation, boolean clipRoundedToBottom) { + int left; + int top; + int right; + int bottom; + int height; + Path intersectPath = null; + if (!mCustomOutline) { + int translation = mShouldTranslateContents && !ignoreTranslation + ? (int) getTranslation() : 0; + left = Math.max(translation, 0); + top = mClipTopAmount + mBackgroundTop; + right = getWidth() + Math.min(translation, 0); + bottom = Math.max(getActualHeight(), top); + int intersectBottom = Math.max(getActualHeight() - mClipBottomAmount, top); + if (bottom != intersectBottom) { + if (clipRoundedToBottom) { + bottom = intersectBottom; + } else { + getRoundedRectPath(left, top, right, + intersectBottom, 0.0f, + 0.0f, mTmpPath2); + intersectPath = mTmpPath2; + } + } + } else { + left = mOutlineRect.left; + top = mOutlineRect.top; + right = mOutlineRect.right; + bottom = mOutlineRect.bottom; + } + height = bottom - top; + if (height == 0) { + return EMPTY_PATH; + } + float topRoundness = mAlwaysRoundBothCorners + ? mOutlineRadius : mCurrentTopRoundness * mOutlineRadius; + float bottomRoundness = mAlwaysRoundBothCorners + ? mOutlineRadius : mCurrentBottomRoundness * mOutlineRadius; + if (topRoundness + bottomRoundness > height) { + float overShoot = topRoundness + bottomRoundness - height; + topRoundness -= overShoot * mCurrentTopRoundness + / (mCurrentTopRoundness + mCurrentBottomRoundness); + bottomRoundness -= overShoot * mCurrentBottomRoundness + / (mCurrentTopRoundness + mCurrentBottomRoundness); + } + getRoundedRectPath(left, top, right, bottom, topRoundness, + bottomRoundness, mTmpPath); + Path roundedRectPath = mTmpPath; + if (intersectPath != null) { + roundedRectPath.op(intersectPath, Path.Op.INTERSECT); + } + return roundedRectPath; + } + + public static void getRoundedRectPath(int left, int top, int right, int bottom, + float topRoundness, float bottomRoundness, Path outPath) { + outPath.reset(); + int width = right - left; + float topRoundnessX = topRoundness; + float bottomRoundnessX = bottomRoundness; + topRoundnessX = Math.min(width / 2, topRoundnessX); + bottomRoundnessX = Math.min(width / 2, bottomRoundnessX); + if (topRoundness > 0.0f) { + outPath.moveTo(left, top + topRoundness); + outPath.quadTo(left, top, left + topRoundnessX, top); + outPath.lineTo(right - topRoundnessX, top); + outPath.quadTo(right, top, right, top + topRoundness); + } else { + outPath.moveTo(left, top); + outPath.lineTo(right, top); + } + if (bottomRoundness > 0.0f) { + outPath.lineTo(right, bottom - bottomRoundness); + outPath.quadTo(right, bottom, right - bottomRoundnessX, bottom); + outPath.lineTo(left + bottomRoundnessX, bottom); + outPath.quadTo(left, bottom, left, bottom - bottomRoundness); + } else { + outPath.lineTo(right, bottom); + outPath.lineTo(left, bottom); + } + outPath.close(); + } + public ExpandableOutlineView(Context context, AttributeSet attrs) { super(context, attrs); setOutlineProvider(mProvider); initDimens(); } + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + canvas.save(); + Path intersectPath = null; + if (mClipRoundedToClipTopAmount) { + int left = 0; + int top = (int) (mClipTopAmount - mDistanceToTopRoundness); + int right = getWidth(); + int bottom = (int) Math.max(getActualHeight() - mClipBottomAmount, + top + mOutlineRadius); + ExpandableOutlineView.getRoundedRectPath(left, top, right, bottom, mOutlineRadius, + 0.0f, + mClipPath); + intersectPath = mClipPath; + } + boolean clipped = false; + if (childNeedsClipping(child)) { + Path clipPath = getCustomClipPath(child); + if (clipPath == null) { + clipPath = getClipPath(); + } + if (clipPath != null) { + if (intersectPath != null) { + clipPath.op(intersectPath, Path.Op.INTERSECT); + } + canvas.clipPath(clipPath); + clipped = true; + } + } + if (!clipped && intersectPath != null) { + canvas.clipPath(intersectPath); + } + boolean result = super.drawChild(canvas, child, drawingTime); + canvas.restore(); + return result; + } + + @Override + public void setDistanceToTopRoundness(float distanceToTopRoundness) { + super.setDistanceToTopRoundness(distanceToTopRoundness); + if (distanceToTopRoundness != mDistanceToTopRoundness) { + mClipRoundedToClipTopAmount = distanceToTopRoundness >= 0; + mDistanceToTopRoundness = distanceToTopRoundness; + invalidate(); + } + } + + protected boolean childNeedsClipping(View child) { + return false; + } + + protected boolean isClippingNeeded() { + return mAlwaysRoundBothCorners || mCustomOutline || getTranslation() != 0 ; + + } + private void initDimens() { Resources res = getResources(); mShouldTranslateContents = res.getBoolean(R.bool.config_translateNotificationContentsOnSwipe); mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius); - setClipToOutline(res.getBoolean(R.bool.config_clipNotificationsToOutline)); + mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline); + if (!mAlwaysRoundBothCorners) { + mOutlineRadius = res.getDimensionPixelSize( + Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); + } + setClipToOutline(mAlwaysRoundBothCorners); + } + + public void setTopRoundness(float topRoundness, boolean animate) { + if (mTopRoundness != topRoundness) { + mTopRoundness = topRoundness; + PropertyAnimator.setProperty(this, TOP_ROUNDNESS, topRoundness, + ROUNDNESS_PROPERTIES, animate); + } + } + + protected void applyRoundness() { + invalidateOutline(); + invalidate(); + } + + public float getCurrentBackgroundRadiusTop() { + return mCurrentTopRoundness * mOutlineRadius; + } + + public float getCurrentTopRoundness() { + return mCurrentTopRoundness; + } + + public float getCurrentBottomRoundness() { + return mCurrentBottomRoundness; + } + + protected float getCurrentBackgroundRadiusBottom() { + return mCurrentBottomRoundness * mOutlineRadius; + } + + public void setBottomRoundness(float bottomRoundness, boolean animate) { + if (mBottomRoundness != bottomRoundness) { + mBottomRoundness = bottomRoundness; + PropertyAnimator.setProperty(this, BOTTOM_ROUNDNESS, bottomRoundness, + ROUNDNESS_PROPERTIES, animate); + } + } + + protected void setBackgroundTop(int backgroundTop) { + if (mBackgroundTop != backgroundTop) { + mBackgroundTop = backgroundTop; + invalidateOutline(); + } + } + + private void setTopRoundnessInternal(float topRoundness) { + mCurrentTopRoundness = topRoundness; + applyRoundness(); + } + + private void setBottomRoundnessInternal(float bottomRoundness) { + mCurrentBottomRoundness = bottomRoundness; + applyRoundness(); } public void onDensityOrFontScaleChanged() { initDimens(); - invalidateOutline(); + applyRoundness(); } @Override public void setActualHeight(int actualHeight, boolean notifyListeners) { + int previousHeight = getActualHeight(); super.setActualHeight(actualHeight, notifyListeners); - invalidateOutline(); + if (previousHeight != actualHeight) { + applyRoundness(); + } } @Override public void setClipTopAmount(int clipTopAmount) { + int previousAmount = getClipTopAmount(); super.setClipTopAmount(clipTopAmount); - invalidateOutline(); + if (previousAmount != clipTopAmount) { + applyRoundness(); + } } @Override public void setClipBottomAmount(int clipBottomAmount) { + int previousAmount = getClipBottomAmount(); super.setClipBottomAmount(clipBottomAmount); - invalidateOutline(); + if (previousAmount != clipBottomAmount) { + applyRoundness(); + } } protected void setOutlineAlpha(float alpha) { if (alpha != mOutlineAlpha) { mOutlineAlpha = alpha; - invalidateOutline(); + applyRoundness(); } } @@ -113,8 +369,7 @@ public abstract class ExpandableOutlineView extends ExpandableView { setOutlineRect(rect.left, rect.top, rect.right, rect.bottom); } else { mCustomOutline = false; - setClipToOutline(false); - invalidateOutline(); + applyRoundness(); } } @@ -151,15 +406,16 @@ public abstract class ExpandableOutlineView extends ExpandableView { protected void setOutlineRect(float left, float top, float right, float bottom) { mCustomOutline = true; - setClipToOutline(true); mOutlineRect.set((int) left, (int) top, (int) right, (int) bottom); // Outlines need to be at least 1 dp mOutlineRect.bottom = (int) Math.max(top, mOutlineRect.bottom); mOutlineRect.right = (int) Math.max(left, mOutlineRect.right); - - invalidateOutline(); + applyRoundness(); } + public Path getCustomClipPath(View child) { + return null; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java index aac9af8a0234..1496a41a9b47 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java @@ -130,6 +130,14 @@ public abstract class ExpandableView extends FrameLayout { } } + /** + * Set the distance to the top roundness, from where we should start clipping a value above + * or equal to 0 is the effective distance, and if a value below 0 is received, there should + * be no clipping. + */ + public void setDistanceToTopRoundness(float distanceToTopRoundness) { + } + public void setActualHeight(int actualHeight) { setActualHeight(actualHeight, true /* notifyListeners */); } @@ -143,6 +151,10 @@ public abstract class ExpandableView extends FrameLayout { return mActualHeight; } + public boolean isExpandAnimationRunning() { + return false; + } + /** * @return The maximum height of this notification. */ @@ -202,6 +214,10 @@ public abstract class ExpandableView extends FrameLayout { return mDark; } + public boolean isRemoved() { + return false; + } + /** * See {@link #setHideSensitive}. This is a variant which notifies this view in advance about * the upcoming state of hiding sensitive notifications. It gets called at the very beginning @@ -363,8 +379,8 @@ public abstract class ExpandableView extends FrameLayout { return false; } - private void updateClipping() { - if (mClipToActualHeight) { + protected void updateClipping() { + if (mClipToActualHeight && shouldClipToActualHeight()) { int top = getClipTopAmount(); mClipRect.set(0, top, getWidth(), Math.max(getActualHeight() + getExtraBottomPadding() - mClipBottomAmount, top)); @@ -374,6 +390,10 @@ public abstract class ExpandableView extends FrameLayout { } } + protected boolean shouldClipToActualHeight() { + return true; + } + public void setClipToActualHeight(boolean clipToActualHeight) { mClipToActualHeight = clipToActualHeight; updateClipping(); @@ -474,6 +494,9 @@ public abstract class ExpandableView extends FrameLayout { return false; } + public void setHeadsUpIsVisible() { + } + public boolean isChildInGroup() { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java index d370a6331ea1..2d16d2209c9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java @@ -433,24 +433,27 @@ public final class KeyboardShortcuts { // Assist. final AssistUtils assistUtils = new AssistUtils(mContext); final ComponentName assistComponent = assistUtils.getAssistComponentForUser(userId); - PackageInfo assistPackageInfo = null; - try { - assistPackageInfo = mPackageManager.getPackageInfo( - assistComponent.getPackageName(), 0, userId); - } catch (RemoteException e) { - Log.e(TAG, "PackageManagerService is dead"); - } + // Not all devices have an assist component. + if (assistComponent != null) { + PackageInfo assistPackageInfo = null; + try { + assistPackageInfo = mPackageManager.getPackageInfo( + assistComponent.getPackageName(), 0, userId); + } catch (RemoteException e) { + Log.e(TAG, "PackageManagerService is dead"); + } - if (assistPackageInfo != null) { - final Icon assistIcon = Icon.createWithResource( - assistPackageInfo.applicationInfo.packageName, - assistPackageInfo.applicationInfo.icon); + if (assistPackageInfo != null) { + final Icon assistIcon = Icon.createWithResource( + assistPackageInfo.applicationInfo.packageName, + assistPackageInfo.applicationInfo.icon); - keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo( - mContext.getString(R.string.keyboard_shortcut_group_applications_assist), - assistIcon, - KeyEvent.KEYCODE_UNKNOWN, - KeyEvent.META_META_ON)); + keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo( + mContext.getString(R.string.keyboard_shortcut_group_applications_assist), + assistIcon, + KeyEvent.KEYCODE_UNKNOWN, + KeyEvent.META_META_ON)); + } } // Browser. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 569e58d78fae..b7a15005b170 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -44,6 +46,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.Utils; import com.android.systemui.Dependency; +import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; import com.android.systemui.statusbar.phone.LockIcon; @@ -52,6 +55,10 @@ import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.util.wakelock.SettableWakeLock; import com.android.systemui.util.wakelock.WakeLock; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.text.NumberFormat; + /** * Controls the indications and error messages shown on the Keyguard */ @@ -78,7 +85,7 @@ public class KeyguardIndicationController { private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private String mRestingIndication; - private String mTransientIndication; + private CharSequence mTransientIndication; private int mTransientTextColor; private int mInitialTextColor; private boolean mVisible; @@ -87,6 +94,7 @@ public class KeyguardIndicationController { private boolean mPowerCharged; private int mChargingSpeed; private int mChargingWattage; + private int mBatteryLevel; private String mMessageToShowOnScreenOn; private KeyguardUpdateMonitorCallback mUpdateMonitorCallback; @@ -113,11 +121,9 @@ public class KeyguardIndicationController { WakeLock wakeLock) { mContext = context; mIndicationArea = indicationArea; - mTextView = (KeyguardIndicationTextView) indicationArea.findViewById( - R.id.keyguard_indication_text); + mTextView = indicationArea.findViewById(R.id.keyguard_indication_text); mInitialTextColor = mTextView != null ? mTextView.getCurrentTextColor() : Color.WHITE; - mDisclosure = (KeyguardIndicationTextView) indicationArea.findViewById( - R.id.keyguard_indication_enterprise_disclosure); + mDisclosure = indicationArea.findViewById(R.id.keyguard_indication_enterprise_disclosure); mLockIcon = lockIcon; mWakeLock = new SettableWakeLock(wakeLock); @@ -189,7 +195,7 @@ public class KeyguardIndicationController { if (!mHandler.hasMessages(MSG_HIDE_TRANSIENT)) { hideTransientIndication(); } - updateIndication(); + updateIndication(false); } else if (!visible) { // If we unlock and return to keyguard quickly, previous error should not be shown hideTransientIndication(); @@ -201,7 +207,7 @@ public class KeyguardIndicationController { */ public void setRestingIndication(String restingIndication) { mRestingIndication = restingIndication; - updateIndication(); + updateIndication(false); } /** @@ -246,14 +252,14 @@ public class KeyguardIndicationController { /** * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}. */ - public void showTransientIndication(String transientIndication) { + public void showTransientIndication(CharSequence transientIndication) { showTransientIndication(transientIndication, mInitialTextColor); } /** * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}. */ - public void showTransientIndication(String transientIndication, int textColor) { + public void showTransientIndication(CharSequence transientIndication, int textColor) { mTransientIndication = transientIndication; mTransientTextColor = textColor; mHandler.removeMessages(MSG_HIDE_TRANSIENT); @@ -262,7 +268,8 @@ public class KeyguardIndicationController { mWakeLock.setAcquired(true); hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS); } - updateIndication(); + + updateIndication(false); } /** @@ -272,11 +279,11 @@ public class KeyguardIndicationController { if (mTransientIndication != null) { mTransientIndication = null; mHandler.removeMessages(MSG_HIDE_TRANSIENT); - updateIndication(); + updateIndication(false); } } - protected final void updateIndication() { + protected final void updateIndication(boolean animate) { if (TextUtils.isEmpty(mTransientIndication)) { mWakeLock.setAcquired(false); } @@ -285,14 +292,46 @@ public class KeyguardIndicationController { // Walk down a precedence-ordered list of what indication // should be shown based on user or device state if (mDozing) { - // If we're dozing, never show a persistent indication. + mTextView.setTextColor(Color.WHITE); if (!TextUtils.isEmpty(mTransientIndication)) { // When dozing we ignore any text color and use white instead, because // colors can be hard to read in low brightness. - mTextView.setTextColor(Color.WHITE); mTextView.switchIndication(mTransientIndication); + } else if (mPowerPluggedIn) { + String indication = computePowerIndication(); + if (animate) { + int yTranslation = mContext.getResources().getInteger( + R.integer.wired_charging_aod_text_animation_distance); + int animateUpDuration = mContext.getResources().getInteger( + R.integer.wired_charging_aod_text_animation_duration_up); + int animateDownDuration = mContext.getResources().getInteger( + R.integer.wired_charging_aod_text_animation_duration_down); + mTextView.animate() + .translationYBy(yTranslation) + .setInterpolator(Interpolators.LINEAR) + .setDuration(animateUpDuration) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mTextView.switchIndication(indication); + } + @Override + public void onAnimationEnd(Animator animation) { + mTextView.animate() + .setDuration(animateDownDuration) + .setInterpolator(Interpolators.BOUNCE) + .translationYBy(-1 * yTranslation) + .setListener(null); + } + }); + } else { + mTextView.switchIndication(indication); + } + } else { - mTextView.switchIndication(null); + String percentage = NumberFormat.getPercentInstance() + .format(mBatteryLevel / 100f); + mTextView.switchIndication(percentage); } return; } @@ -383,7 +422,7 @@ public class KeyguardIndicationController { public void onReceive(Context context, Intent intent) { mHandler.post(() -> { if (mVisible) { - updateIndication(); + updateIndication(false); } }); } @@ -405,10 +444,25 @@ public class KeyguardIndicationController { return; } mDozing = dozing; - updateIndication(); + updateIndication(false); updateDisclosure(); } + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("KeyguardIndicationController:"); + pw.println(" mTransientTextColor: " + Integer.toHexString(mTransientTextColor)); + pw.println(" mInitialTextColor: " + Integer.toHexString(mInitialTextColor)); + pw.println(" mPowerPluggedIn: " + mPowerPluggedIn); + pw.println(" mPowerCharged: " + mPowerCharged); + pw.println(" mChargingSpeed: " + mChargingSpeed); + pw.println(" mChargingWattage: " + mChargingWattage); + pw.println(" mMessageToShowOnScreenOn: " + mMessageToShowOnScreenOn); + pw.println(" mDozing: " + mDozing); + pw.println(" mBatteryLevel: " + mBatteryLevel); + pw.println(" mTextView.getText(): " + (mTextView == null ? null : mTextView.getText())); + pw.println(" computePowerIndication(): " + computePowerIndication()); + } + protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback { public static final int HIDE_DELAY_MS = 5000; private int mLastSuccessiveErrorMessage = -1; @@ -422,7 +476,8 @@ public class KeyguardIndicationController { mPowerCharged = status.isCharged(); mChargingWattage = status.maxChargingWattage; mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold); - updateIndication(); + mBatteryLevel = status.level; + updateIndication(!wasPluggedIn && mPowerPluggedIn); if (mDozing) { if (!wasPluggedIn && mPowerPluggedIn) { showTransientIndication(computePowerIndication()); @@ -490,6 +545,12 @@ public class KeyguardIndicationController { } @Override + public void onTrustAgentErrorMessage(CharSequence message) { + int errorColor = Utils.getColorError(mContext); + showTransientIndication(message, errorColor); + } + + @Override public void onScreenTurnedOn() { if (mMessageToShowOnScreenOn != null) { int errorColor = Utils.getColorError(mContext); @@ -522,7 +583,7 @@ public class KeyguardIndicationController { @Override public void onUserUnlocked() { if (mVisible) { - updateIndication(); + updateIndication(false); } } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java index 81a99bc1e70c..ab89a5287cdb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java @@ -19,37 +19,70 @@ package com.android.systemui.statusbar; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Canvas; -import android.graphics.ColorFilter; import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.RippleDrawable; import android.util.AttributeSet; import android.view.View; +import com.android.systemui.Interpolators; +import com.android.systemui.R; +import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; + /** * A view that can be used for both the dimmed and normal background of an notification. */ public class NotificationBackgroundView extends View { + private final boolean mDontModifyCorners; private Drawable mBackground; private int mClipTopAmount; private int mActualHeight; private int mClipBottomAmount; private int mTintColor; + private float[] mCornerRadii = new float[8]; + private boolean mBottomIsRounded; + private int mBackgroundTop; + private boolean mBottomAmountClips = true; + private boolean mExpandAnimationRunning; + private float mActualWidth; + private int mDrawableAlpha = 255; public NotificationBackgroundView(Context context, AttributeSet attrs) { super(context, attrs); + mDontModifyCorners = getResources().getBoolean( + R.bool.config_clipNotificationsToOutline); } @Override protected void onDraw(Canvas canvas) { - draw(canvas, mBackground); + if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop + || mExpandAnimationRunning) { + canvas.save(); + if (!mExpandAnimationRunning) { + canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount); + } + draw(canvas, mBackground); + canvas.restore(); + } } private void draw(Canvas canvas, Drawable drawable) { - int bottom = mActualHeight - mClipBottomAmount; - if (drawable != null && bottom > mClipTopAmount) { - drawable.setBounds(0, mClipTopAmount, getWidth(), bottom); + if (drawable != null) { + int bottom = mActualHeight; + if (mBottomIsRounded && mBottomAmountClips && !mExpandAnimationRunning) { + bottom -= mClipBottomAmount; + } + int left = 0; + int right = getWidth(); + if (mExpandAnimationRunning) { + left = (int) ((getWidth() - mActualWidth) / 2.0f); + right = (int) (left + mActualWidth); + } + drawable.setBounds(left, mBackgroundTop, right, bottom); drawable.draw(canvas); } } @@ -87,6 +120,7 @@ public class NotificationBackgroundView extends View { unscheduleDrawable(mBackground); } mBackground = background; + mBackground.mutate(); if (mBackground != null) { mBackground.setCallback(this); setTint(mTintColor); @@ -94,6 +128,7 @@ public class NotificationBackgroundView extends View { if (mBackground instanceof RippleDrawable) { ((RippleDrawable) mBackground).setForceSoftware(true); } + updateBackgroundRadii(); invalidate(); } @@ -113,6 +148,9 @@ public class NotificationBackgroundView extends View { } public void setActualHeight(int actualHeight) { + if (mExpandAnimationRunning) { + return; + } mActualHeight = actualHeight; invalidate(); } @@ -150,6 +188,74 @@ public class NotificationBackgroundView extends View { } public void setDrawableAlpha(int drawableAlpha) { + mDrawableAlpha = drawableAlpha; + if (mExpandAnimationRunning) { + return; + } mBackground.setAlpha(drawableAlpha); } + + public void setRoundness(float topRoundness, float bottomRoundNess) { + mBottomIsRounded = bottomRoundNess != 0.0f; + mCornerRadii[0] = topRoundness; + mCornerRadii[1] = topRoundness; + mCornerRadii[2] = topRoundness; + mCornerRadii[3] = topRoundness; + mCornerRadii[4] = bottomRoundNess; + mCornerRadii[5] = bottomRoundNess; + mCornerRadii[6] = bottomRoundNess; + mCornerRadii[7] = bottomRoundNess; + updateBackgroundRadii(); + } + + public void setBottomAmountClips(boolean clips) { + if (clips != mBottomAmountClips) { + mBottomAmountClips = clips; + invalidate(); + } + } + + private void updateBackgroundRadii() { + if (mDontModifyCorners) { + return; + } + if (mBackground instanceof LayerDrawable) { + GradientDrawable gradientDrawable = + (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0); + gradientDrawable.setCornerRadii(mCornerRadii); + } + } + + public void setBackgroundTop(int backgroundTop) { + mBackgroundTop = backgroundTop; + invalidate(); + } + + public void setExpandAnimationParams(ActivityLaunchAnimator.ExpandAnimationParameters params) { + mActualHeight = params.getHeight(); + mActualWidth = params.getWidth(); + float alphaProgress = Interpolators.ALPHA_IN.getInterpolation( + params.getProgress( + ActivityLaunchAnimator.ANIMATION_DURATION_FADE_CONTENT /* delay */, + ActivityLaunchAnimator.ANIMATION_DURATION_FADE_APP /* duration */)); + mBackground.setAlpha((int) (mDrawableAlpha * (1.0f - alphaProgress))); + invalidate(); + } + + public void setExpandAnimationRunning(boolean running) { + mExpandAnimationRunning = running; + if (mBackground instanceof LayerDrawable) { + GradientDrawable gradientDrawable = + (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0); + gradientDrawable.setXfermode( + running ? new PorterDuffXfermode(PorterDuff.Mode.SRC) : null); + // Speed optimization: disable AA if transfer mode is not SRC_OVER. AA is not easy to + // spot during animation anyways. + gradientDrawable.setAntiAlias(!running); + } + if (!mExpandAnimationRunning) { + setDrawableAlpha(mDrawableAlpha); + } + invalidate(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 9e059c89ffe1..c4d0b79a69c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -24,23 +24,28 @@ import android.graphics.Rect; import android.os.Build; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; +import android.util.Log; import android.view.NotificationHeaderView; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.LinearLayout; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.NotificationColorUtil; +import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.statusbar.notification.HybridNotificationView; import com.android.systemui.statusbar.notification.HybridGroupManager; +import com.android.systemui.statusbar.notification.HybridNotificationView; import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.NotificationViewWrapper; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.policy.RemoteInputView; +import com.android.systemui.statusbar.policy.SmartReplyConstants; +import com.android.systemui.statusbar.policy.SmartReplyView; /** * A frame layout containing the actual payload of the notification, including the contracted, @@ -49,6 +54,7 @@ import com.android.systemui.statusbar.policy.RemoteInputView; */ public class NotificationContentView extends FrameLayout { + private static final String TAG = "NotificationContentView"; public static final int VISIBLE_TYPE_CONTRACTED = 0; public static final int VISIBLE_TYPE_EXPANDED = 1; public static final int VISIBLE_TYPE_HEADSUP = 2; @@ -58,9 +64,9 @@ public class NotificationContentView extends FrameLayout { public static final int UNDEFINED = -1; private final Rect mClipBounds = new Rect(); - private final int mMinContractedHeight; - private final int mNotificationContentMarginEnd; + private int mMinContractedHeight; + private int mNotificationContentMarginEnd; private View mContractedChild; private View mExpandedChild; private View mHeadsUpChild; @@ -71,6 +77,9 @@ public class NotificationContentView extends FrameLayout { private RemoteInputView mExpandedRemoteInput; private RemoteInputView mHeadsUpRemoteInput; + private SmartReplyConstants mSmartReplyConstants; + private SmartReplyView mExpandedSmartReplyView; + private NotificationViewWrapper mContractedWrapper; private NotificationViewWrapper mExpandedWrapper; private NotificationViewWrapper mHeadsUpWrapper; @@ -139,6 +148,11 @@ public class NotificationContentView extends FrameLayout { public NotificationContentView(Context context, AttributeSet attrs) { super(context, attrs); mHybridGroupManager = new HybridGroupManager(getContext(), this); + mSmartReplyConstants = Dependency.get(SmartReplyConstants.class); + initView(); + } + + public void initView() { mMinContractedHeight = getResources().getDimensionPixelSize( R.dimen.min_notification_layout_height); mNotificationContentMarginEnd = getResources().getDimensionPixelSize( @@ -178,7 +192,7 @@ public class NotificationContentView extends FrameLayout { : MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY : MeasureSpec.AT_MOST); - mExpandedChild.measure(widthMeasureSpec, spec); + measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, spec, 0); maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight()); } if (mContractedChild != null) { @@ -196,22 +210,22 @@ public class NotificationContentView extends FrameLayout { } else { heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST); } - mContractedChild.measure(widthMeasureSpec, heightSpec); + measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0); int measuredHeight = mContractedChild.getMeasuredHeight(); if (measuredHeight < mMinContractedHeight) { heightSpec = MeasureSpec.makeMeasureSpec(mMinContractedHeight, MeasureSpec.EXACTLY); - mContractedChild.measure(widthMeasureSpec, heightSpec); + measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0); } maxChildHeight = Math.max(maxChildHeight, measuredHeight); if (updateContractedHeaderWidth()) { - mContractedChild.measure(widthMeasureSpec, heightSpec); + measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0); } if (mExpandedChild != null && mContractedChild.getMeasuredHeight() > mExpandedChild.getMeasuredHeight()) { // the Expanded child is smaller then the collapsed. Let's remeasure it. heightSpec = MeasureSpec.makeMeasureSpec(mContractedChild.getMeasuredHeight(), MeasureSpec.EXACTLY); - mExpandedChild.measure(widthMeasureSpec, heightSpec); + measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, heightSpec, 0); } } if (mHeadsUpChild != null) { @@ -223,9 +237,9 @@ public class NotificationContentView extends FrameLayout { size = Math.min(size, layoutParams.height); useExactly = true; } - mHeadsUpChild.measure(widthMeasureSpec, + measureChildWithMargins(mHeadsUpChild, widthMeasureSpec, 0, MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY - : MeasureSpec.AT_MOST)); + : MeasureSpec.AT_MOST), 0); maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight()); } if (mSingleLineView != null) { @@ -384,6 +398,22 @@ public class NotificationContentView extends FrameLayout { mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */); } + private NotificationViewWrapper getWrapperForView(View child) { + if (child == mContractedChild) { + return mContractedWrapper; + } + if (child == mExpandedChild) { + return mExpandedWrapper; + } + if (child == mHeadsUpChild) { + return mHeadsUpWrapper; + } + if (child == mAmbientChild) { + return mAmbientWrapper; + } + return null; + } + public void setExpandedChild(View child) { if (mExpandedChild != null) { mPreviousExpandedRemoteInputIntent = null; @@ -643,6 +673,13 @@ public class NotificationContentView extends FrameLayout { int endHeight = getViewForVisibleType(mVisibleType).getHeight(); int progress = Math.abs(mContentHeight - startHeight); int totalDistance = Math.abs(endHeight - startHeight); + if (totalDistance == 0) { + Log.wtf(TAG, "the total transformation distance is 0" + + "\n StartType: " + mTransformationStartVisibleType + " height: " + startHeight + + "\n VisibleType: " + mVisibleType + " height: " + endHeight + + "\n mContentHeight: " + mContentHeight); + return 1.0f; + } float amount = (float) progress / (float) totalDistance; return Math.min(1.0f, amount); } @@ -1096,7 +1133,7 @@ public class NotificationContentView extends FrameLayout { if (mAmbientChild != null) { mAmbientWrapper.onContentUpdated(entry.row); } - applyRemoteInput(entry); + applyRemoteInputAndSmartReply(entry); updateLegacy(); mForceSelectNextLayout = true; setDark(mDark, false /* animate */, 0 /* delay */); @@ -1128,20 +1165,33 @@ public class NotificationContentView extends FrameLayout { } } - private void applyRemoteInput(final NotificationData.Entry entry) { + private void applyRemoteInputAndSmartReply(final NotificationData.Entry entry) { if (mRemoteInputController == null) { return; } + boolean enableSmartReplies = mSmartReplyConstants.isEnabled(); + boolean hasRemoteInput = false; + RemoteInput remoteInputWithChoices = null; + PendingIntent pendingIntentWithChoices = null; Notification.Action[] actions = entry.notification.getNotification().actions; if (actions != null) { for (Notification.Action a : actions) { if (a.getRemoteInputs() != null) { for (RemoteInput ri : a.getRemoteInputs()) { - if (ri.getAllowFreeFormInput()) { + boolean showRemoteInputView = ri.getAllowFreeFormInput(); + boolean showSmartReplyView = enableSmartReplies && ri.getChoices() != null + && ri.getChoices().length > 0; + if (showRemoteInputView) { hasRemoteInput = true; + } + if (showSmartReplyView) { + remoteInputWithChoices = ri; + pendingIntentWithChoices = a.actionIntent; + } + if (showRemoteInputView || showSmartReplyView) { break; } } @@ -1149,6 +1199,11 @@ public class NotificationContentView extends FrameLayout { } } + applyRemoteInput(entry, hasRemoteInput); + applySmartReplyView(remoteInputWithChoices, pendingIntentWithChoices); + } + + private void applyRemoteInput(NotificationData.Entry entry, boolean hasRemoteInput) { View bigContentView = mExpandedChild; if (bigContentView != null) { mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasRemoteInput, @@ -1245,6 +1300,40 @@ public class NotificationContentView extends FrameLayout { return null; } + private void applySmartReplyView(RemoteInput remoteInput, PendingIntent pendingIntent) { + mExpandedSmartReplyView = mExpandedChild == null ? + null : applySmartReplyView(mExpandedChild, remoteInput, pendingIntent); + } + + private SmartReplyView applySmartReplyView( + View view, RemoteInput remoteInput, PendingIntent pendingIntent) { + View smartReplyContainerCandidate = view.findViewById( + com.android.internal.R.id.smart_reply_container); + if (!(smartReplyContainerCandidate instanceof LinearLayout)) { + return null; + } + LinearLayout smartReplyContainer = (LinearLayout) smartReplyContainerCandidate; + if (remoteInput == null || pendingIntent == null) { + smartReplyContainer.setVisibility(View.GONE); + return null; + } + SmartReplyView smartReplyView = null; + if (smartReplyContainer.getChildCount() == 0) { + smartReplyView = SmartReplyView.inflate(mContext, smartReplyContainer); + smartReplyContainer.addView(smartReplyView); + } else if (smartReplyContainer.getChildCount() == 1) { + View child = smartReplyContainer.getChildAt(0); + if (child instanceof SmartReplyView) { + smartReplyView = (SmartReplyView) child; + } + } + if (smartReplyView != null) { + smartReplyView.setRepliesFromRemoteInput(remoteInput, pendingIntent); + smartReplyContainer.setVisibility(View.VISIBLE); + } + return smartReplyView; + } + public void closeRemoteInput() { if (mHeadsUpRemoteInput != null) { mHeadsUpRemoteInput.close(); @@ -1459,4 +1548,32 @@ public class NotificationContentView extends FrameLayout { } return false; } + + public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) { + boolean needsPaddings = shouldClipToRounding(getVisibleType(), topRounded, bottomRounded); + if (mUserExpanding) { + needsPaddings |= shouldClipToRounding(mTransformationStartVisibleType, topRounded, + bottomRounded); + } + return needsPaddings; + } + + private boolean shouldClipToRounding(int visibleType, boolean topRounded, + boolean bottomRounded) { + NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType); + if (visibleWrapper == null) { + return false; + } + return visibleWrapper.shouldClipToRounding(topRounded, bottomRounded); + } + + public CharSequence getActiveRemoteInputText() { + if (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive()) { + return mExpandedRemoteInput.getText(); + } + if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive()) { + return mHeadsUpRemoteInput.getText(); + } + return null; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index d0417b59448d..127f3f918fba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -67,6 +67,7 @@ public class NotificationData { public static final class Entry { private static final long LAUNCH_COOLDOWN = 2000; + private static final long REMOTE_INPUT_COOLDOWN = 500; private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN; private static final int COLOR_INVALID = 1; public String key; @@ -86,10 +87,14 @@ public class NotificationData { public RemoteViews cachedAmbientContentView; public CharSequence remoteInputText; public List<SnoozeCriterion> snoozeCriteria; + public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL; + private int mCachedContrastColor = COLOR_INVALID; private int mCachedContrastColorIsFor = COLOR_INVALID; private InflationTask mRunningTask = null; private Throwable mDebugThrowable; + public CharSequence remoteInputTextWhenReset; + public long lastRemoteInputSent = NOT_LAUNCHED_YET; public Entry(StatusBarNotification n) { this.key = n.getKey(); @@ -130,6 +135,10 @@ public class NotificationData { return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN; } + public boolean hasJustSentRemoteInput() { + return SystemClock.elapsedRealtime() < lastRemoteInputSent + REMOTE_INPUT_COOLDOWN; + } + /** * Create the icons for a notification * @param context the context to create the icons with @@ -263,6 +272,11 @@ public class NotificationData { public Throwable getDebugThrowable() { return mDebugThrowable; } + + public void onRemoteInputInserted() { + lastRemoteInputSent = NOT_LAUNCHED_YET; + remoteInputTextWhenReset = null; + } } private final ArrayMap<String, Entry> mEntries = new ArrayMap<>(); @@ -463,6 +477,7 @@ public class NotificationData { } entry.channel = getChannel(entry.key); entry.snoozeCriteria = getSnoozeCriteria(entry.key); + entry.userSentiment = mTmpRanking.getUserSentiment(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java new file mode 100644 index 000000000000..7360486ac7e9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java @@ -0,0 +1,979 @@ +/* + * 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 static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; +import static com.android.systemui.statusbar.NotificationRemoteInputManager + .FORCE_REMOTE_INPUT_HISTORY; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.os.Build; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.provider.Settings; +import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationStats; +import android.service.notification.StatusBarNotification; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.EventLog; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.util.NotificationMessagingUtil; +import com.android.systemui.DejankUtils; +import com.android.systemui.Dependency; +import com.android.systemui.Dumpable; +import com.android.systemui.EventLogTags; +import com.android.systemui.ForegroundServiceController; +import com.android.systemui.R; +import com.android.systemui.UiOffloadThread; +import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.statusbar.notification.InflationException; +import com.android.systemui.statusbar.notification.NotificationInflater; +import com.android.systemui.statusbar.notification.RowInflaterTask; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.util.leak.LeakDetector; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * NotificationEntryManager is responsible for the adding, removing, and updating of notifications. + * It also handles tasks such as their inflation and their interaction with other + * Notification.*Manager objects. + */ +public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback, + ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler, + VisualStabilityManager.Callback { + private static final String TAG = "NotificationEntryManager"; + 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"; + + protected final NotificationMessagingUtil mMessagingUtil; + protected final Context mContext; + protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>(); + protected final NotificationClicker mNotificationClicker = new NotificationClicker(); + protected final ArraySet<NotificationData.Entry> mHeadsUpEntriesToRemoveOnSwitch = + new ArraySet<>(); + + // Dependencies: + protected final NotificationLockscreenUserManager mLockscreenUserManager = + Dependency.get(NotificationLockscreenUserManager.class); + protected final NotificationGroupManager mGroupManager = + Dependency.get(NotificationGroupManager.class); + protected final NotificationGutsManager mGutsManager = + Dependency.get(NotificationGutsManager.class); + protected final NotificationRemoteInputManager mRemoteInputManager = + Dependency.get(NotificationRemoteInputManager.class); + protected final NotificationMediaManager mMediaManager = + Dependency.get(NotificationMediaManager.class); + protected final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); + protected final DeviceProvisionedController mDeviceProvisionedController = + Dependency.get(DeviceProvisionedController.class); + protected final VisualStabilityManager mVisualStabilityManager = + Dependency.get(VisualStabilityManager.class); + protected final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class); + protected final ForegroundServiceController mForegroundServiceController = + Dependency.get(ForegroundServiceController.class); + protected final NotificationListener mNotificationListener = + Dependency.get(NotificationListener.class); + + protected IStatusBarService mBarService; + protected NotificationPresenter mPresenter; + protected Callback mCallback; + protected PowerManager mPowerManager; + protected SystemServicesProxy mSystemServicesProxy; + protected NotificationListenerService.RankingMap mLatestRankingMap; + protected HeadsUpManager mHeadsUpManager; + protected NotificationData mNotificationData; + protected ContentObserver mHeadsUpObserver; + protected boolean mUseHeadsUp = false; + protected boolean mDisableNotificationAlerts; + protected NotificationListContainer mListContainer; + + private final class NotificationClicker implements View.OnClickListener { + + @Override + public void onClick(final View v) { + if (!(v instanceof ExpandableNotificationRow)) { + Log.e(TAG, "NotificationClicker called on a view that is not a notification row."); + return; + } + + mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), v); + + final ExpandableNotificationRow row = (ExpandableNotificationRow) v; + final StatusBarNotification sbn = row.getStatusBarNotification(); + if (sbn == null) { + Log.e(TAG, "NotificationClicker called on an unclickable notification,"); + return; + } + + // Check if the notification is displaying the menu, if so slide notification back + if (row.getProvider() != null && row.getProvider().isMenuVisible()) { + row.animateTranslateNotification(0); + return; + } + + // Mark notification for one frame. + row.setJustClicked(true); + DejankUtils.postAfterTraversal(() -> row.setJustClicked(false)); + + mCallback.onNotificationClicked(sbn, row); + } + + public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { + Notification notification = sbn.getNotification(); + if (notification.contentIntent != null || notification.fullScreenIntent != null) { + row.setOnClickListener(this); + } else { + row.setOnClickListener(null); + } + } + } + + private final DeviceProvisionedController.DeviceProvisionedListener + mDeviceProvisionedListener = + new DeviceProvisionedController.DeviceProvisionedListener() { + @Override + public void onDeviceProvisionedChanged() { + updateNotifications(); + } + }; + + public NotificationListenerService.RankingMap getLatestRankingMap() { + return mLatestRankingMap; + } + + public void setLatestRankingMap(NotificationListenerService.RankingMap latestRankingMap) { + mLatestRankingMap = latestRankingMap; + } + + public void setDisableNotificationAlerts(boolean disableNotificationAlerts) { + mDisableNotificationAlerts = disableNotificationAlerts; + mHeadsUpObserver.onChange(true); + } + + public void destroy() { + mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener); + } + + public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { + if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) { + removeNotification(entry.key, getLatestRankingMap()); + mHeadsUpEntriesToRemoveOnSwitch.remove(entry); + if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) { + setLatestRankingMap(null); + } + } else { + updateNotificationRanking(null); + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("NotificationEntryManager state:"); + pw.print(" mPendingNotifications="); + if (mPendingNotifications.size() == 0) { + pw.println("null"); + } else { + for (NotificationData.Entry entry : mPendingNotifications.values()) { + pw.println(entry.notification); + } + } + pw.print(" mUseHeadsUp="); + pw.println(mUseHeadsUp); + } + + public NotificationEntryManager(Context context) { + mContext = context; + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + mMessagingUtil = new NotificationMessagingUtil(context); + mSystemServicesProxy = SystemServicesProxy.getInstance(mContext); + } + + public void setUpWithPresenter(NotificationPresenter presenter, + NotificationListContainer listContainer, Callback callback, + HeadsUpManager headsUpManager) { + mPresenter = presenter; + mCallback = callback; + mNotificationData = new NotificationData(presenter); + mHeadsUpManager = headsUpManager; + mNotificationData.setHeadsUpManager(mHeadsUpManager); + mListContainer = listContainer; + + mHeadsUpObserver = new ContentObserver(mPresenter.getHandler()) { + @Override + public void onChange(boolean selfChange) { + boolean wasUsing = mUseHeadsUp; + mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts + && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, + Settings.Global.HEADS_UP_OFF); + Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled")); + if (wasUsing != mUseHeadsUp) { + if (!mUseHeadsUp) { + Log.d(TAG, + "dismissing any existing heads up notification on disable event"); + mHeadsUpManager.releaseAllImmediately(); + } + } + } + }; + + if (ENABLE_HEADS_UP) { + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), + true, + mHeadsUpObserver); + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true, + mHeadsUpObserver); + } + + mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); + + mHeadsUpObserver.onChange(true); // set up + } + + public NotificationData getNotificationData() { + return mNotificationData; + } + + public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() { + return mGutsManager::openGuts; + } + + @Override + public void logNotificationExpansion(String key, boolean userAction, boolean expanded) { + mUiOffloadThread.submit(() -> { + try { + mBarService.onNotificationExpansionChanged(key, userAction, expanded); + } catch (RemoteException e) { + // Ignore. + } + }); + } + + @Override + public void onReorderingAllowed() { + updateNotifications(); + } + + private boolean shouldSuppressFullScreenIntent(String key) { + if (mPresenter.isDeviceInVrMode()) { + return true; + } + + if (mPowerManager.isInteractive()) { + return mNotificationData.shouldSuppressScreenOn(key); + } else { + return mNotificationData.shouldSuppressScreenOff(key); + } + } + + private void inflateViews(NotificationData.Entry entry, ViewGroup parent) { + PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext, + entry.notification.getUser().getIdentifier()); + + final StatusBarNotification sbn = entry.notification; + if (entry.row != null) { + entry.reset(); + updateNotification(entry, pmUser, sbn, entry.row); + } else { + new RowInflaterTask().inflate(mContext, parent, entry, + row -> { + bindRow(entry, pmUser, sbn, row); + updateNotification(entry, pmUser, sbn, row); + }); + } + } + + private void bindRow(NotificationData.Entry entry, PackageManager pmUser, + StatusBarNotification sbn, ExpandableNotificationRow row) { + row.setExpansionLogger(this, entry.notification.getKey()); + row.setGroupManager(mGroupManager); + row.setHeadsUpManager(mHeadsUpManager); + row.setOnExpandClickListener(mPresenter); + row.setInflationCallback(this); + row.setLongPressListener(getNotificationLongClicker()); + mRemoteInputManager.bindRow(row); + + // Get the app name. + // Note that Notification.Builder#bindHeaderAppName has similar logic + // but since this field is used in the guts, it must be accurate. + // Therefore we will only show the application label, or, failing that, the + // package name. No substitutions. + final String pkg = sbn.getPackageName(); + String appname = pkg; + try { + final ApplicationInfo info = pmUser.getApplicationInfo(pkg, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS); + if (info != null) { + appname = String.valueOf(pmUser.getApplicationLabel(info)); + } + } catch (PackageManager.NameNotFoundException e) { + // Do nothing + } + row.setAppName(appname); + row.setOnDismissRunnable(() -> + performRemoveNotification(row.getStatusBarNotification())); + row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + if (ENABLE_REMOTE_INPUT) { + row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); + } + + mCallback.onBindRow(entry, pmUser, sbn, row); + } + + public void performRemoveNotification(StatusBarNotification n) { + NotificationData.Entry entry = mNotificationData.get(n.getKey()); + mRemoteInputManager.onPerformRemoveNotification(n, entry); + final String pkg = n.getPackageName(); + final String tag = n.getTag(); + final int id = n.getId(); + final int userId = n.getUserId(); + try { + int dismissalSurface = NotificationStats.DISMISSAL_SHADE; + if (isHeadsUp(n.getKey())) { + dismissalSurface = NotificationStats.DISMISSAL_PEEK; + } else if (mListContainer.hasPulsingNotifications()) { + dismissalSurface = NotificationStats.DISMISSAL_AOD; + } + mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface); + removeNotification(n.getKey(), null); + + } catch (RemoteException ex) { + // system process is dead if we're here. + } + + mCallback.onPerformRemoveNotification(n); + } + + /** + * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService + * about the failure. + * + * WARNING: this will call back into us. Don't hold any locks. + */ + void handleNotificationError(StatusBarNotification n, String message) { + removeNotification(n.getKey(), null); + try { + mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), + n.getInitialPid(), message, n.getUserId()); + } catch (RemoteException ex) { + // The end is nigh. + } + } + + private void abortExistingInflation(String key) { + if (mPendingNotifications.containsKey(key)) { + NotificationData.Entry entry = mPendingNotifications.get(key); + entry.abortTask(); + mPendingNotifications.remove(key); + } + NotificationData.Entry addedEntry = mNotificationData.get(key); + if (addedEntry != null) { + addedEntry.abortTask(); + } + } + + @Override + public void handleInflationException(StatusBarNotification notification, Exception e) { + handleNotificationError(notification, e.getMessage()); + } + + private void addEntry(NotificationData.Entry shadeEntry) { + boolean isHeadsUped = shouldPeek(shadeEntry); + if (isHeadsUped) { + mHeadsUpManager.showNotification(shadeEntry); + // Mark as seen immediately + setNotificationShown(shadeEntry.notification); + } + addNotificationViews(shadeEntry); + mCallback.onNotificationAdded(shadeEntry); + } + + @Override + public void onAsyncInflationFinished(NotificationData.Entry entry) { + mPendingNotifications.remove(entry.key); + // If there was an async task started after the removal, we don't want to add it back to + // the list, otherwise we might get leaks. + boolean isNew = mNotificationData.get(entry.key) == null; + if (isNew && !entry.row.isRemoved()) { + addEntry(entry); + } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) { + mVisualStabilityManager.onLowPriorityUpdated(entry); + mPresenter.updateNotificationViews(); + } + entry.row.setLowPriorityStateUpdated(false); + } + + @Override + public void removeNotification(String key, NotificationListenerService.RankingMap ranking) { + boolean deferRemoval = false; + abortExistingInflation(key); + if (mHeadsUpManager.isHeadsUp(key)) { + // A cancel() in response to a remote input shouldn't be delayed, as it makes the + // sending look longer than it takes. + // Also we should not defer the removal if reordering isn't allowed since otherwise + // some notifications can't disappear before the panel is closed. + boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key) + && !FORCE_REMOTE_INPUT_HISTORY + || !mVisualStabilityManager.isReorderingAllowed(); + deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime); + } + mMediaManager.onNotificationRemoved(key); + + NotificationData.Entry entry = mNotificationData.get(key); + if (FORCE_REMOTE_INPUT_HISTORY + && shouldKeepForRemoteInput(entry) + && entry.row != null && !entry.row.isDismissed()) { + StatusBarNotification sbn = entry.notification; + + Notification.Builder b = Notification.Builder + .recoverBuilder(mContext, sbn.getNotification().clone()); + CharSequence[] oldHistory = sbn.getNotification().extras + .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); + CharSequence[] newHistory; + if (oldHistory == null) { + newHistory = new CharSequence[1]; + } else { + newHistory = new CharSequence[oldHistory.length + 1]; + System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length); + } + CharSequence remoteInputText = entry.remoteInputText; + if (TextUtils.isEmpty(remoteInputText)) { + remoteInputText = entry.remoteInputTextWhenReset; + } + newHistory[0] = String.valueOf(remoteInputText); + b.setRemoteInputHistory(newHistory); + + Notification newNotification = b.build(); + + // Undo any compatibility view inflation + newNotification.contentView = sbn.getNotification().contentView; + newNotification.bigContentView = sbn.getNotification().bigContentView; + newNotification.headsUpContentView = sbn.getNotification().headsUpContentView; + + StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(), + sbn.getOpPkg(), + sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), + newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); + boolean updated = false; + entry.onRemoteInputInserted(); + try { + updateNotificationInternal(newSbn, null); + updated = true; + } catch (InflationException e) { + deferRemoval = false; + } + if (updated) { + Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key); + mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key); + return; + } + } + if (deferRemoval) { + mLatestRankingMap = ranking; + mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key)); + return; + } + + if (mRemoteInputManager.onRemoveNotification(entry)) { + mLatestRankingMap = ranking; + return; + } + + if (entry != null && mGutsManager.getExposedGuts() != null + && mGutsManager.getExposedGuts() == entry.row.getGuts() + && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) { + Log.w(TAG, "Keeping notification because it's showing guts. " + key); + mLatestRankingMap = ranking; + mGutsManager.setKeyToRemoveOnGutsClosed(key); + return; + } + + if (entry != null) { + mForegroundServiceController.removeNotification(entry.notification); + } + + if (entry != null && entry.row != null) { + entry.row.setRemoved(); + mListContainer.cleanUpViewState(entry.row); + } + // Let's remove the children if this was a summary + handleGroupSummaryRemoved(key); + StatusBarNotification old = removeNotificationViews(key, ranking); + + mCallback.onNotificationRemoved(key, old); + } + + private boolean shouldKeepForRemoteInput(NotificationData.Entry entry) { + if (entry == null) { + return false; + } + if (mRemoteInputManager.getController().isSpinning(entry.key)) { + return true; + } + if (entry.hasJustSentRemoteInput()) { + return true; + } + return false; + } + + private StatusBarNotification removeNotificationViews(String key, + NotificationListenerService.RankingMap ranking) { + NotificationData.Entry entry = mNotificationData.remove(key, ranking); + if (entry == null) { + Log.w(TAG, "removeNotification for unknown key: " + key); + return null; + } + updateNotifications(); + Dependency.get(LeakDetector.class).trackGarbage(entry); + return entry.notification; + } + + /** + * Ensures that the group children are cancelled immediately when the group summary is cancelled + * instead of waiting for the notification manager to send all cancels. Otherwise this could + * lead to flickers. + * + * This also ensures that the animation looks nice and only consists of a single disappear + * animation instead of multiple. + * @param key the key of the notification was removed + * + */ + private void handleGroupSummaryRemoved(String key) { + NotificationData.Entry entry = mNotificationData.get(key); + if (entry != null && entry.row != null + && entry.row.isSummaryWithChildren()) { + if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) { + // We don't want to remove children for autobundled notifications as they are not + // always cancelled. We only remove them if they were dismissed by the user. + return; + } + List<ExpandableNotificationRow> notificationChildren = + entry.row.getNotificationChildren(); + for (int i = 0; i < notificationChildren.size(); i++) { + ExpandableNotificationRow row = notificationChildren.get(i); + if ((row.getStatusBarNotification().getNotification().flags + & Notification.FLAG_FOREGROUND_SERVICE) != 0) { + // the child is a foreground service notification which we can't remove! + continue; + } + row.setKeepInParent(true); + // we need to set this state earlier as otherwise we might generate some weird + // animations + row.setRemoved(); + } + } + } + + public void updateNotificationsOnDensityOrFontScaleChanged() { + ArrayList<NotificationData.Entry> activeNotifications = + mNotificationData.getActiveNotifications(); + for (int i = 0; i < activeNotifications.size(); i++) { + NotificationData.Entry entry = activeNotifications.get(i); + boolean exposedGuts = mGutsManager.getExposedGuts() != null + && entry.row.getGuts() == mGutsManager.getExposedGuts(); + entry.row.onDensityOrFontScaleChanged(); + if (exposedGuts) { + mGutsManager.onDensityOrFontScaleChanged(entry.row); + } + } + } + + private void updateNotification(NotificationData.Entry entry, PackageManager pmUser, + StatusBarNotification sbn, ExpandableNotificationRow row) { + row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry)); + boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey()); + boolean isUpdate = mNotificationData.get(entry.key) != null; + boolean wasLowPriority = row.isLowPriority(); + row.setIsLowPriority(isLowPriority); + row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority)); + // bind the click event to the content area + mNotificationClicker.register(row, sbn); + + // Extract target SDK version. + try { + ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0); + entry.targetSdk = info.targetSdkVersion; + } catch (PackageManager.NameNotFoundException ex) { + Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); + } + row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD + && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); + entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); + entry.autoRedacted = entry.notification.getNotification().publicVersion == null; + + entry.row = row; + entry.row.setOnActivatedListener(mPresenter); + + boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn, + mNotificationData.getImportance(sbn.getKey())); + boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight + && !mPresenter.isPresenterFullyCollapsed(); + row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); + row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); + row.updateNotification(entry); + } + + + protected void addNotificationViews(NotificationData.Entry entry) { + if (entry == null) { + return; + } + // Add the expanded view and icon. + mNotificationData.add(entry); + updateNotifications(); + } + + protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) + throws InflationException { + if (DEBUG) { + Log.d(TAG, "createNotificationViews(notification=" + sbn); + } + NotificationData.Entry entry = new NotificationData.Entry(sbn); + Dependency.get(LeakDetector.class).trackInstance(entry); + entry.createIcons(mContext, sbn); + // Construct the expanded view. + inflateViews(entry, mListContainer.getViewParentForNotification(entry)); + return entry; + } + + private void addNotificationInternal(StatusBarNotification notification, + NotificationListenerService.RankingMap ranking) throws InflationException { + String key = notification.getKey(); + if (DEBUG) Log.d(TAG, "addNotification key=" + key); + + mNotificationData.updateRanking(ranking); + NotificationData.Entry shadeEntry = createNotificationViews(notification); + boolean isHeadsUped = shouldPeek(shadeEntry); + if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) { + if (shouldSuppressFullScreenIntent(key)) { + if (DEBUG) { + Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key); + } + } else if (mNotificationData.getImportance(key) + < NotificationManager.IMPORTANCE_HIGH) { + if (DEBUG) { + Log.d(TAG, "No Fullscreen intent: not important enough: " + + key); + } + } else { + // Stop screensaver if the notification has a fullscreen intent. + // (like an incoming phone call) + SystemServicesProxy.getInstance(mContext).awakenDreamsAsync(); + + // not immersive & a fullscreen alert should be shown + if (DEBUG) + Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); + try { + EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, + key); + notification.getNotification().fullScreenIntent.send(); + shadeEntry.notifyFullScreenIntentLaunched(); + mMetricsLogger.count("note_fullscreen", 1); + } catch (PendingIntent.CanceledException e) { + } + } + } + abortExistingInflation(key); + + mForegroundServiceController.addNotification(notification, + mNotificationData.getImportance(key)); + + mPendingNotifications.put(key, shadeEntry); + } + + @Override + public void addNotification(StatusBarNotification notification, + NotificationListenerService.RankingMap ranking) { + try { + addNotificationInternal(notification, ranking); + } catch (InflationException e) { + handleInflationException(notification, e); + } + } + + private boolean alertAgain(NotificationData.Entry oldEntry, Notification newNotification) { + return oldEntry == null || !oldEntry.hasInterrupted() + || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; + } + + private void updateNotificationInternal(StatusBarNotification notification, + NotificationListenerService.RankingMap ranking) throws InflationException { + if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); + + final String key = notification.getKey(); + abortExistingInflation(key); + NotificationData.Entry entry = mNotificationData.get(key); + if (entry == null) { + return; + } + mHeadsUpEntriesToRemoveOnSwitch.remove(entry); + mRemoteInputManager.onUpdateNotification(entry); + + if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) { + mGutsManager.setKeyToRemoveOnGutsClosed(null); + Log.w(TAG, "Notification that was kept for guts was updated. " + key); + } + + Notification n = notification.getNotification(); + mNotificationData.updateRanking(ranking); + + final StatusBarNotification oldNotification = entry.notification; + entry.notification = notification; + mGroupManager.onEntryUpdated(entry, oldNotification); + + entry.updateIcons(mContext, notification); + inflateViews(entry, mListContainer.getViewParentForNotification(entry)); + + mForegroundServiceController.updateNotification(notification, + mNotificationData.getImportance(key)); + + boolean shouldPeek = shouldPeek(entry, notification); + boolean alertAgain = alertAgain(entry, n); + + updateHeadsUp(key, entry, shouldPeek, alertAgain); + updateNotifications(); + + if (!notification.isClearable()) { + // The user may have performed a dismiss action on the notification, since it's + // not clearable we should snap it back. + mListContainer.snapViewIfNeeded(entry.row); + } + + if (DEBUG) { + // Is this for you? + boolean isForCurrentUser = mPresenter.isNotificationForCurrentProfiles(notification); + Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); + } + + mCallback.onNotificationUpdated(notification); + } + + @Override + public void updateNotification(StatusBarNotification notification, + NotificationListenerService.RankingMap ranking) { + try { + updateNotificationInternal(notification, ranking); + } catch (InflationException e) { + handleInflationException(notification, e); + } + } + + public void updateNotifications() { + mNotificationData.filterAndSort(); + + mPresenter.updateNotificationViews(); + } + + public void updateNotificationRanking(NotificationListenerService.RankingMap ranking) { + mNotificationData.updateRanking(ranking); + updateNotifications(); + } + + protected boolean shouldPeek(NotificationData.Entry entry) { + return shouldPeek(entry, entry.notification); + } + + public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) { + if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) { + if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode"); + return false; + } + + if (mNotificationData.shouldFilterOut(sbn)) { + if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey()); + return false; + } + + boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming(); + + if (!inUse && !mPresenter.isDozing()) { + if (DEBUG) { + Log.d(TAG, "No peeking: not in use: " + sbn.getKey()); + } + return false; + } + + if (!mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) { + if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); + return false; + } + + if (mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOff(sbn.getKey())) { + if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); + return false; + } + + if (entry.hasJustLaunchedFullScreenIntent()) { + if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey()); + return false; + } + + if (isSnoozedPackage(sbn)) { + if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey()); + return false; + } + + // Allow peeking for DEFAULT notifications only if we're on Ambient Display. + int importanceLevel = mPresenter.isDozing() ? NotificationManager.IMPORTANCE_DEFAULT + : NotificationManager.IMPORTANCE_HIGH; + if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) { + if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey()); + return false; + } + + // Don't peek notifications that are suppressed due to group alert behavior + if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) { + if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior"); + return false; + } + + if (!mCallback.shouldPeek(entry, sbn)) { + return false; + } + + return true; + } + + protected void setNotificationShown(StatusBarNotification n) { + setNotificationsShown(new String[]{n.getKey()}); + } + + protected void setNotificationsShown(String[] keys) { + try { + mNotificationListener.setNotificationsShown(keys); + } catch (RuntimeException e) { + Log.d(TAG, "failed setNotificationsShown: ", e); + } + } + + protected boolean isSnoozedPackage(StatusBarNotification sbn) { + return mHeadsUpManager.isSnoozed(sbn.getPackageName()); + } + + protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek, + boolean alertAgain) { + final boolean wasHeadsUp = isHeadsUp(key); + if (wasHeadsUp) { + if (!shouldPeek) { + // We don't want this to be interrupting anymore, lets remove it + mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */); + } else { + mHeadsUpManager.updateNotification(entry, alertAgain); + } + } else if (shouldPeek && alertAgain) { + // This notification was updated to be a heads-up, show it! + mHeadsUpManager.showNotification(entry); + } + } + + protected boolean isHeadsUp(String key) { + return mHeadsUpManager.isHeadsUp(key); + } + + /** + * Callback for NotificationEntryManager. + */ + public interface Callback { + + /** + * Called when a new entry is created. + * + * @param shadeEntry entry that was created + */ + void onNotificationAdded(NotificationData.Entry shadeEntry); + + /** + * Called when a notification was updated. + * + * @param notification notification that was updated + */ + void onNotificationUpdated(StatusBarNotification notification); + + /** + * Called when a notification was removed. + * + * @param key key of notification that was removed + * @param old StatusBarNotification of the notification before it was removed + */ + void onNotificationRemoved(String key, StatusBarNotification old); + + + /** + * Called when a notification is clicked. + * + * @param sbn notification that was clicked + * @param row row for that notification + */ + void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row); + + /** + * Called when a new notification and row is created. + * + * @param entry entry for the notification + * @param pmUser package manager for user + * @param sbn notification + * @param row row for the notification + */ + void onBindRow(NotificationData.Entry entry, PackageManager pmUser, + StatusBarNotification sbn, ExpandableNotificationRow row); + + /** + * Removes a notification immediately. + * + * @param statusBarNotification notification that is being removed + */ + void onPerformRemoveNotification(StatusBarNotification statusBarNotification); + + /** + * Returns true if NotificationEntryManager should peek this notification. + * + * @param entry entry of the notification that might be peeked + * @param sbn notification that might be peeked + * @return true if the notification should be peeked + */ + boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java index 54d622b341f1..52776d7e8430 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java @@ -18,46 +18,22 @@ package com.android.systemui.statusbar; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.app.INotificationManager; -import android.app.NotificationChannel; -import android.app.NotificationManager; import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.os.Handler; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.service.notification.NotificationListenerService; -import android.service.notification.StatusBarNotification; +import android.support.annotation.Nullable; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import android.view.ViewAnimationUtils; -import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.SeekBar; -import android.widget.Switch; -import android.widget.TextView; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settingslib.Utils; + import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; -import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.statusbar.stack.StackStateAnimator; -import java.util.Set; - /** * The guts of a notification revealed when performing a long press. */ @@ -212,6 +188,12 @@ public class NotificationGuts extends FrameLayout { } } + public void openControls( + int x, int y, boolean needsFalsingProtection, @Nullable Runnable onAnimationEnd) { + animateOpen(x, y, onAnimationEnd); + setExposed(true /* exposed */, needsFalsingProtection); + } + public void closeControls(boolean leavebehinds, boolean controls, int x, int y, boolean force) { if (mGutsContent != null) { if (mGutsContent.isLeavebehind() && leavebehinds) { @@ -239,6 +221,27 @@ public class NotificationGuts extends FrameLayout { } } + private void animateOpen(int x, int y, @Nullable Runnable onAnimationEnd) { + final double horz = Math.max(getWidth() - x, x); + final double vert = Math.max(getHeight() - y, y); + final float r = (float) Math.hypot(horz, vert); + + final Animator a + = ViewAnimationUtils.createCircularReveal(this, x, y, 0, r); + a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); + a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); + a.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (onAnimationEnd != null) { + onAnimationEnd.run(); + } + } + }); + a.start(); + } + private void animateClose(int x, int y) { if (x == -1 || y == -1) { x = (getLeft() + getRight()) / 2; @@ -304,7 +307,7 @@ public class NotificationGuts extends FrameLayout { } } - public void setExposed(boolean exposed, boolean needsFalsingProtection) { + private void setExposed(boolean exposed, boolean needsFalsingProtection) { final boolean wasExposed = mExposed; mExposed = exposed; mNeedsFalsingProtection = needsFalsingProtection; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java new file mode 100644 index 000000000000..1aaa3b2593d2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java @@ -0,0 +1,360 @@ +/* + * 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 static android.service.notification.NotificationListenerService.Ranking + .USER_SENTIMENT_NEGATIVE; + +import android.app.INotificationManager; +import android.app.NotificationChannel; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.provider.Settings; +import android.service.notification.StatusBarNotification; +import android.util.ArraySet; +import android.util.Log; +import android.view.HapticFeedbackConstants; +import android.view.View; +import android.view.accessibility.AccessibilityManager; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; +import com.android.systemui.Dependency; +import com.android.systemui.Dumpable; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.statusbar.phone.StatusBar; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and + * closing guts, and keeping track of the currently exposed notification guts. + */ +public class NotificationGutsManager implements Dumpable { + private static final String TAG = "NotificationGutsManager"; + + // Must match constant in Settings. Used to highlight preferences when linking to Settings. + private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; + + private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); + private final Set<String> mNonBlockablePkgs; + private final Context mContext; + private final AccessibilityManager mAccessibilityManager; + + // Dependencies: + private final NotificationLockscreenUserManager mLockscreenUserManager = + Dependency.get(NotificationLockscreenUserManager.class); + + // which notification is currently being longpress-examined by the user + private NotificationGuts mNotificationGutsExposed; + private NotificationMenuRowPlugin.MenuItem mGutsMenuItem; + protected NotificationPresenter mPresenter; + protected NotificationEntryManager mEntryManager; + private NotificationListContainer mListContainer; + private NotificationInfo.CheckSaveListener mCheckSaveListener; + private OnSettingsClickListener mOnSettingsClickListener; + private String mKeyToRemoveOnGutsClosed; + + public NotificationGutsManager(Context context) { + mContext = context; + Resources res = context.getResources(); + + mNonBlockablePkgs = new HashSet<>(); + Collections.addAll(mNonBlockablePkgs, res.getStringArray( + com.android.internal.R.array.config_nonBlockableNotificationPackages)); + + mAccessibilityManager = (AccessibilityManager) + mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); + } + + public void setUpWithPresenter(NotificationPresenter presenter, + NotificationEntryManager entryManager, NotificationListContainer listContainer, + NotificationInfo.CheckSaveListener checkSaveListener, + OnSettingsClickListener onSettingsClickListener) { + mPresenter = presenter; + mEntryManager = entryManager; + mListContainer = listContainer; + mCheckSaveListener = checkSaveListener; + mOnSettingsClickListener = onSettingsClickListener; + } + + public String getKeyToRemoveOnGutsClosed() { + return mKeyToRemoveOnGutsClosed; + } + + public void setKeyToRemoveOnGutsClosed(String keyToRemoveOnGutsClosed) { + mKeyToRemoveOnGutsClosed = keyToRemoveOnGutsClosed; + } + + public void onDensityOrFontScaleChanged(ExpandableNotificationRow row) { + setExposedGuts(row.getGuts()); + bindGuts(row); + } + + private void saveAndCloseNotificationMenu( + ExpandableNotificationRow row, NotificationGuts guts, View done) { + guts.resetFalsingCheck(); + int[] rowLocation = new int[2]; + int[] doneLocation = new int[2]; + row.getLocationOnScreen(rowLocation); + done.getLocationOnScreen(doneLocation); + + final int centerX = done.getWidth() / 2; + final int centerY = done.getHeight() / 2; + final int x = doneLocation[0] - rowLocation[0] + centerX; + final int y = doneLocation[1] - rowLocation[1] + centerY; + closeAndSaveGuts(false /* removeLeavebehind */, false /* force */, + true /* removeControls */, x, y, true /* resetMenu */); + } + + /** + * Sends an intent to open the notification settings for a particular package and optional + * channel. + */ + private void startAppNotificationSettingsActivity(String packageName, final int appUid, + final NotificationChannel channel, ExpandableNotificationRow row) { + final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); + intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName); + intent.putExtra(Settings.EXTRA_APP_UID, appUid); + if (channel != null) { + intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId()); + } + mPresenter.startNotificationGutsIntent(intent, appUid, row); + } + + public void bindGuts(final ExpandableNotificationRow row) { + bindGuts(row, mGutsMenuItem); + } + + private void bindGuts(final ExpandableNotificationRow row, + NotificationMenuRowPlugin.MenuItem item) { + row.inflateGuts(); + row.setGutsView(item); + final StatusBarNotification sbn = row.getStatusBarNotification(); + row.setTag(sbn.getPackageName()); + final NotificationGuts guts = row.getGuts(); + guts.setClosedListener((NotificationGuts g) -> { + if (!g.willBeRemoved() && !row.isRemoved()) { + mListContainer.onHeightChanged( + row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */); + } + if (mNotificationGutsExposed == g) { + mNotificationGutsExposed = null; + mGutsMenuItem = null; + } + String key = sbn.getKey(); + if (key.equals(mKeyToRemoveOnGutsClosed)) { + mKeyToRemoveOnGutsClosed = null; + mEntryManager.removeNotification(key, mEntryManager.getLatestRankingMap()); + } + }); + + View gutsView = item.getGutsView(); + if (gutsView instanceof NotificationSnooze) { + NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView; + snoozeGuts.setSnoozeListener(mListContainer.getSwipeActionHelper()); + snoozeGuts.setStatusBarNotification(sbn); + snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria); + guts.setHeightChangedListener((NotificationGuts g) -> { + mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */); + }); + } + + if (gutsView instanceof NotificationInfo) { + final UserHandle userHandle = sbn.getUser(); + PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext, + userHandle.getIdentifier()); + final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + final String pkg = sbn.getPackageName(); + NotificationInfo info = (NotificationInfo) gutsView; + // Settings link is only valid for notifications that specify a user, unless this is the + // system user. + NotificationInfo.OnSettingsClickListener onSettingsClick = null; + if (!userHandle.equals(UserHandle.ALL) + || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) { + onSettingsClick = (View v, NotificationChannel channel, int appUid) -> { + mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO); + guts.resetFalsingCheck(); + mOnSettingsClickListener.onClick(sbn.getKey()); + startAppNotificationSettingsActivity(pkg, appUid, channel, row); + }; + } + final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v, + Intent intent) -> { + mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS); + guts.resetFalsingCheck(); + mPresenter.startNotificationGutsIntent(intent, sbn.getUid(), row); + }; + final View.OnClickListener onDoneClick = (View v) -> { + saveAndCloseNotificationMenu(row, guts, v); + }; + + ArraySet<NotificationChannel> channels = new ArraySet<>(); + channels.add(row.getEntry().channel); + if (row.isSummaryWithChildren()) { + // If this is a summary, then add in the children notification channels for the + // same user and pkg. + final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren(); + final int numChildren = childrenRows.size(); + for (int i = 0; i < numChildren; i++) { + final ExpandableNotificationRow childRow = childrenRows.get(i); + final NotificationChannel childChannel = childRow.getEntry().channel; + final StatusBarNotification childSbn = childRow.getStatusBarNotification(); + if (childSbn.getUser().equals(userHandle) && + childSbn.getPackageName().equals(pkg)) { + channels.add(childChannel); + } + } + } + try { + info.bindNotification(pmUser, iNotificationManager, pkg, row.getEntry().channel, + channels.size(), sbn, mCheckSaveListener, onSettingsClick, + onAppSettingsClick, mNonBlockablePkgs, + row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } + } + } + + /** + * Closes guts or notification menus that might be visible and saves any changes. + * + * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed. + * @param force true if guts should be closed regardless of state (used for snooze only). + * @param removeControls true if controls (e.g. info) should be closed. + * @param x if closed based on touch location, this is the x touch location. + * @param y if closed based on touch location, this is the y touch location. + * @param resetMenu if any notification menus that might be revealed should be closed. + */ + public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls, + int x, int y, boolean resetMenu) { + if (mNotificationGutsExposed != null) { + mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force); + } + if (resetMenu) { + mListContainer.resetExposedMenuView(false /* animate */, true /* force */); + } + } + + /** + * Returns the exposed NotificationGuts or null if none are exposed. + */ + public NotificationGuts getExposedGuts() { + return mNotificationGutsExposed; + } + + public void setExposedGuts(NotificationGuts guts) { + mNotificationGutsExposed = guts; + } + + /** + * Opens guts on the given ExpandableNotificationRow |v|. + * + * @param v ExpandableNotificationRow to open guts on + * @param x x coordinate of origin of circular reveal + * @param y y coordinate of origin of circular reveal + * @param item MenuItem the guts should display + * @return true if guts was opened + */ + public boolean openGuts(View v, int x, int y, + NotificationMenuRowPlugin.MenuItem item) { + if (!(v instanceof ExpandableNotificationRow)) { + return false; + } + + if (v.getWindowToken() == null) { + Log.e(TAG, "Trying to show notification guts, but not attached to window"); + return false; + } + + final ExpandableNotificationRow row = (ExpandableNotificationRow) v; + if (row.isDark()) { + return false; + } + v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + if (row.areGutsExposed()) { + closeAndSaveGuts(false /* removeLeavebehind */, false /* force */, + true /* removeControls */, -1 /* x */, -1 /* y */, + true /* resetMenu */); + return false; + } + bindGuts(row, item); + NotificationGuts guts = row.getGuts(); + + // Assume we are a status_bar_notification_row + if (guts == null) { + // This view has no guts. Examples are the more card or the dismiss all view + return false; + } + + mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS); + + // ensure that it's laid but not visible until actually laid out + guts.setVisibility(View.INVISIBLE); + // Post to ensure the the guts are properly laid out. + guts.post(new Runnable() { + @Override + public void run() { + if (row.getWindowToken() == null) { + Log.e(TAG, "Trying to show notification guts, but not attached to " + + "window"); + return; + } + closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, + true /* removeControls */, -1 /* x */, -1 /* y */, + false /* resetMenu */); + guts.setVisibility(View.VISIBLE); + + final boolean needsFalsingProtection = + (mPresenter.isPresenterLocked() && + !mAccessibilityManager.isTouchExplorationEnabled()); + guts.openControls(x, y, needsFalsingProtection, () -> { + // Move the notification view back over the menu + row.resetTranslation(); + }); + + row.closeRemoteInput(); + mListContainer.onHeightChanged(row, true /* needsAnimation */); + mNotificationGutsExposed = guts; + mGutsMenuItem = item; + } + }); + return true; + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("NotificationGutsManager state:"); + pw.print(" mKeyToRemoveOnGutsClosed: "); + pw.println(mKeyToRemoveOnGutsClosed); + } + + public interface OnSettingsClickListener { + void onClick(String key); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java index 43018174de44..112707552e6e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java @@ -128,6 +128,7 @@ public class NotificationHeaderUtil { mComparators.add(HeaderProcessor.forTextView(mRow, com.android.internal.R.id.header_text)); mDividers.add(com.android.internal.R.id.header_text_divider); + mDividers.add(com.android.internal.R.id.header_text_secondary_divider); mDividers.add(com.android.internal.R.id.time_divider); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java index 3b23a0c0db89..afe906c28ea5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java @@ -18,6 +18,10 @@ package com.android.systemui.statusbar; import static android.app.NotificationManager.IMPORTANCE_NONE; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; @@ -38,15 +42,14 @@ import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.Switch; import android.widget.TextView; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; +import com.android.systemui.Interpolators; import com.android.systemui.R; -import java.lang.IllegalArgumentException; import java.util.List; import java.util.Set; @@ -57,24 +60,37 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private static final String TAG = "InfoGuts"; private INotificationManager mINotificationManager; + private PackageManager mPm; + private String mPkg; private String mAppName; private int mAppUid; - private List<NotificationChannel> mNotificationChannels; + private int mNumNotificationChannels; private NotificationChannel mSingleNotificationChannel; + private int mStartingUserImportance; + private int mChosenImportance; private boolean mIsSingleDefaultChannel; + private boolean mNonblockable; private StatusBarNotification mSbn; - private int mStartingUserImportance; + private AnimatorSet mExpandAnimation; - private TextView mNumChannelsView; - private View mChannelDisabledView; - private TextView mSettingsLinkView; - private Switch mChannelEnabledSwitch; private CheckSaveListener mCheckSaveListener; + private OnSettingsClickListener mOnSettingsClickListener; private OnAppSettingsClickListener mAppSettingsClickListener; - private PackageManager mPm; - private NotificationGuts mGutsContainer; + private boolean mNegativeUserSentiment; + + private OnClickListener mOnKeepShowing = v -> { + closeControls(v); + }; + + private OnClickListener mOnStopNotifications = v -> { + swapContent(false); + }; + + private OnClickListener mOnUndo = v -> { + swapContent(true); + }; public NotificationInfo(Context context, AttributeSet attrs) { super(context, attrs); @@ -84,7 +100,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G public interface CheckSaveListener { // Invoked when importance has changed and the NotificationInfo wants to try to save it. // Listener should run saveImportance unless the change should be canceled. - void checkSave(Runnable saveImportance); + void checkSave(Runnable saveImportance, StatusBarNotification sbn); } public interface OnSettingsClickListener { @@ -98,141 +114,110 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G public void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager, final String pkg, - final List<NotificationChannel> notificationChannels, - int startingUserImportance, + final NotificationChannel notificationChannel, + final int numChannels, final StatusBarNotification sbn, - OnSettingsClickListener onSettingsClick, - OnAppSettingsClickListener onAppSettingsClick, - OnClickListener onDoneClick, - CheckSaveListener checkSaveListener, + final CheckSaveListener checkSaveListener, + final OnSettingsClickListener onSettingsClick, + final OnAppSettingsClickListener onAppSettingsClick, final Set<String> nonBlockablePkgs) throws RemoteException { + bindNotification(pm, iNotificationManager, pkg, notificationChannel, numChannels, sbn, + checkSaveListener, onSettingsClick, onAppSettingsClick, nonBlockablePkgs, + false /* negative sentiment */); + } + + public void bindNotification(final PackageManager pm, + final INotificationManager iNotificationManager, + final String pkg, + final NotificationChannel notificationChannel, + final int numChannels, + final StatusBarNotification sbn, + final CheckSaveListener checkSaveListener, + final OnSettingsClickListener onSettingsClick, + final OnAppSettingsClickListener onAppSettingsClick, + final Set<String> nonBlockablePkgs, + boolean negativeUserSentiment) throws RemoteException { mINotificationManager = iNotificationManager; mPkg = pkg; - mNotificationChannels = notificationChannels; - mCheckSaveListener = checkSaveListener; + mNumNotificationChannels = numChannels; mSbn = sbn; mPm = pm; mAppSettingsClickListener = onAppSettingsClick; - mStartingUserImportance = startingUserImportance; mAppName = mPkg; - Drawable pkgicon = null; - CharSequence channelNameText = ""; - ApplicationInfo info = null; - try { - info = pm.getApplicationInfo(mPkg, - PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_DISABLED_COMPONENTS - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_DIRECT_BOOT_AWARE); - if (info != null) { - mAppUid = sbn.getUid(); - mAppName = String.valueOf(pm.getApplicationLabel(info)); - pkgicon = pm.getApplicationIcon(info); - } - } catch (PackageManager.NameNotFoundException e) { - // app is gone, just show package name and generic icon - pkgicon = pm.getDefaultActivityIcon(); - } - ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon); + mCheckSaveListener = checkSaveListener; + mOnSettingsClickListener = onSettingsClick; + mSingleNotificationChannel = notificationChannel; + mStartingUserImportance = mChosenImportance = mSingleNotificationChannel.getImportance(); + mNegativeUserSentiment = negativeUserSentiment; - int numTotalChannels = iNotificationManager.getNumNotificationChannelsForPackage( + int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage( pkg, mAppUid, false /* includeDeleted */); - if (mNotificationChannels.isEmpty()) { + if (mNumNotificationChannels == 0) { throw new IllegalArgumentException("bindNotification requires at least one channel"); } else { - if (mNotificationChannels.size() == 1) { - mSingleNotificationChannel = mNotificationChannels.get(0); - // Special behavior for the Default channel if no other channels have been defined. - mIsSingleDefaultChannel = - (mSingleNotificationChannel.getId() - .equals(NotificationChannel.DEFAULT_CHANNEL_ID) && - numTotalChannels <= 1); - } else { - mSingleNotificationChannel = null; - mIsSingleDefaultChannel = false; - } + // Special behavior for the Default channel if no other channels have been defined. + mIsSingleDefaultChannel = mNumNotificationChannels == 1 + && mSingleNotificationChannel.getId() + .equals(NotificationChannel.DEFAULT_CHANNEL_ID) + && numTotalChannels <= 1; } - boolean nonBlockable = false; try { final PackageInfo pkgInfo = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES); if (Utils.isSystemPackage(getResources(), pm, pkgInfo)) { - final int numChannels = mNotificationChannels.size(); - for (int i = 0; i < numChannels; i++) { - final NotificationChannel notificationChannel = mNotificationChannels.get(i); - // If any of the system channels is not blockable, the bundle is nonblockable - if (!notificationChannel.isBlockableSystem()) { - nonBlockable = true; - break; - } + if (mSingleNotificationChannel != null + && !mSingleNotificationChannel.isBlockableSystem()) { + mNonblockable = true; } } } catch (PackageManager.NameNotFoundException e) { // unlikely. } if (nonBlockablePkgs != null) { - nonBlockable |= nonBlockablePkgs.contains(pkg); + mNonblockable |= nonBlockablePkgs.contains(pkg); } + mNonblockable |= (mNumNotificationChannels > 1); - String channelsDescText; - mNumChannelsView = findViewById(R.id.num_channels_desc); - if (nonBlockable) { - channelsDescText = mContext.getString(R.string.notification_unblockable_desc); - } else if (mIsSingleDefaultChannel) { - channelsDescText = mContext.getString(R.string.notification_default_channel_desc); - } else { - switch (mNotificationChannels.size()) { - case 1: - channelsDescText = String.format(mContext.getResources().getQuantityString( - R.plurals.notification_num_channels_desc, numTotalChannels), - numTotalChannels); - break; - case 2: - channelsDescText = mContext.getString( - R.string.notification_channels_list_desc_2, - mNotificationChannels.get(0).getName(), - mNotificationChannels.get(1).getName()); - break; - default: - final int numOthers = mNotificationChannels.size() - 2; - channelsDescText = String.format( - mContext.getResources().getQuantityString( - R.plurals.notification_channels_list_desc_2_and_others, - numOthers), - mNotificationChannels.get(0).getName(), - mNotificationChannels.get(1).getName(), - numOthers); + bindHeader(); + bindPrompt(); + bindButtons(); + } + + private void bindHeader() throws RemoteException { + // Package name + Drawable pkgicon = null; + ApplicationInfo info; + try { + info = mPm.getApplicationInfo(mPkg, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE); + if (info != null) { + mAppUid = mSbn.getUid(); + mAppName = String.valueOf(mPm.getApplicationLabel(info)); + pkgicon = mPm.getApplicationIcon(info); } + } catch (PackageManager.NameNotFoundException e) { + // app is gone, just show package name and generic icon + pkgicon = mPm.getDefaultActivityIcon(); } - mNumChannelsView.setText(channelsDescText); - - if (mSingleNotificationChannel == null) { - // Multiple channels don't use a channel name for the title. - channelNameText = mContext.getString(R.string.notification_num_channels, - mNotificationChannels.size()); - } else if (mIsSingleDefaultChannel || nonBlockable) { - // If this is the default channel or the app is unblockable, - // don't use our channel-specific text. - channelNameText = mContext.getString(R.string.notification_header_default_channel); - } else { - channelNameText = mSingleNotificationChannel.getName(); - } + ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon); ((TextView) findViewById(R.id.pkgname)).setText(mAppName); - ((TextView) findViewById(R.id.channel_name)).setText(channelNameText); // Set group information if this channel has an associated group. CharSequence groupName = null; if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) { final NotificationChannelGroup notificationChannelGroup = - iNotificationManager.getNotificationChannelGroupForPackage( - mSingleNotificationChannel.getGroup(), pkg, mAppUid); + mINotificationManager.getNotificationChannelGroupForPackage( + mSingleNotificationChannel.getGroup(), mPkg, mAppUid); if (notificationChannelGroup != null) { groupName = notificationChannelGroup.getName(); } } - TextView groupNameView = ((TextView) findViewById(R.id.group_name)); - TextView groupDividerView = ((TextView) findViewById(R.id.pkg_group_divider)); + TextView groupNameView = findViewById(R.id.group_name); + TextView groupDividerView = findViewById(R.id.pkg_group_divider); if (groupName != null) { groupNameView.setText(groupName); groupNameView.setVisibility(View.VISIBLE); @@ -242,53 +227,58 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G groupDividerView.setVisibility(View.GONE); } - bindButtons(nonBlockable); - - // Top-level importance group - mChannelDisabledView = findViewById(R.id.channel_disabled); - updateSecondaryText(); - // Settings button. - final TextView settingsButton = (TextView) findViewById(R.id.more_settings); - if (mAppUid >= 0 && onSettingsClick != null) { + final View settingsButton = findViewById(R.id.info); + if (mAppUid >= 0 && mOnSettingsClickListener != null) { settingsButton.setVisibility(View.VISIBLE); final int appUidF = mAppUid; settingsButton.setOnClickListener( (View view) -> { - onSettingsClick.onClick(view, mSingleNotificationChannel, appUidF); + mOnSettingsClickListener.onClick(view, + mNumNotificationChannels > 1 ? null : mSingleNotificationChannel, + appUidF); }); - if (numTotalChannels <= 1 || nonBlockable) { - settingsButton.setText(R.string.notification_more_settings); - } else { - settingsButton.setText(R.string.notification_all_categories); - } } else { settingsButton.setVisibility(View.GONE); } + } - // Done button. - final TextView doneButton = (TextView) findViewById(R.id.done); - doneButton.setText(R.string.notification_done); - doneButton.setOnClickListener(onDoneClick); + private void bindPrompt() { + final TextView blockPrompt = findViewById(R.id.block_prompt); + bindName(); + if (mNonblockable) { + blockPrompt.setText(R.string.notification_unblockable_desc); + } else { + if (mNegativeUserSentiment) { + blockPrompt.setText(R.string.inline_blocking_helper); + } else if (mIsSingleDefaultChannel || mNumNotificationChannels > 1) { + blockPrompt.setText(R.string.inline_keep_showing_app); + } else { + blockPrompt.setText(R.string.inline_keep_showing); + } + } + } - // Optional settings link - updateAppSettingsLink(); + private void bindName() { + final TextView channelName = findViewById(R.id.channel_name); + if (mIsSingleDefaultChannel || mNumNotificationChannels > 1) { + channelName.setVisibility(View.GONE); + } else { + channelName.setText(mSingleNotificationChannel.getName()); + } } private boolean hasImportanceChanged() { - return mSingleNotificationChannel != null && - mChannelEnabledSwitch != null && - mStartingUserImportance != getSelectedImportance(); + return mSingleNotificationChannel != null && mStartingUserImportance != mChosenImportance; } private void saveImportance() { - if (!hasImportanceChanged()) { + if (mNonblockable) { return; } - final int selectedImportance = getSelectedImportance(); MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE, - selectedImportance - mStartingUserImportance); - mSingleNotificationChannel.setImportance(selectedImportance); + mChosenImportance - mStartingUserImportance); + mSingleNotificationChannel.setImportance(mChosenImportance); mSingleNotificationChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); try { mINotificationManager.updateNotificationChannelForPackage( @@ -298,30 +288,78 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } } - private int getSelectedImportance() { - if (!mChannelEnabledSwitch.isChecked()) { - return IMPORTANCE_NONE; + private void bindButtons() { + View block = findViewById(R.id.block); + block.setOnClickListener(mOnStopNotifications); + TextView keep = findViewById(R.id.keep); + keep.setOnClickListener(mOnKeepShowing); + findViewById(R.id.undo).setOnClickListener(mOnUndo); + + if (mNonblockable) { + keep.setText(R.string.notification_done); + block.setVisibility(GONE); + } + + // app settings link + TextView settingsLinkView = findViewById(R.id.app_settings); + Intent settingsIntent = getAppSettingsIntent(mPm, mPkg, mSingleNotificationChannel, + mSbn.getId(), mSbn.getTag()); + if (settingsIntent != null + && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) { + settingsLinkView.setVisibility(View.VISIBLE); + settingsLinkView.setText(mContext.getString(R.string.notification_app_settings, + mSbn.getNotification().getSettingsText())); + settingsLinkView.setOnClickListener((View view) -> { + mAppSettingsClickListener.onClick(view, settingsIntent); + }); } else { - return mStartingUserImportance; + settingsLinkView.setVisibility(View.GONE); } } - private void bindButtons(final boolean nonBlockable) { - // Enabled Switch - mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch); - mChannelEnabledSwitch.setChecked( - mStartingUserImportance != IMPORTANCE_NONE); - final boolean visible = !nonBlockable && mSingleNotificationChannel != null; - mChannelEnabledSwitch.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); - - // Callback when checked. - mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (mGutsContainer != null) { - mGutsContainer.resetFalsingCheck(); + private void swapContent(boolean showPrompt) { + if (mExpandAnimation != null) { + mExpandAnimation.cancel(); + } + + if (showPrompt) { + mChosenImportance = mStartingUserImportance; + } else { + mChosenImportance = IMPORTANCE_NONE; + } + + View prompt = findViewById(R.id.prompt); + View confirmation = findViewById(R.id.confirmation); + ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA, + prompt.getAlpha(), showPrompt ? 1f : 0f); + promptAnim.setInterpolator(showPrompt ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT); + ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA, + confirmation.getAlpha(), showPrompt ? 0f : 1f); + confirmAnim.setInterpolator(showPrompt ? Interpolators.ALPHA_OUT : Interpolators.ALPHA_IN); + + prompt.setVisibility(showPrompt ? VISIBLE : GONE); + confirmation.setVisibility(showPrompt ? GONE : VISIBLE); + + mExpandAnimation = new AnimatorSet(); + mExpandAnimation.playTogether(promptAnim, confirmAnim); + mExpandAnimation.setDuration(150); + mExpandAnimation.addListener(new AnimatorListenerAdapter() { + boolean cancelled = false; + + @Override + public void onAnimationCancel(Animator animation) { + cancelled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (!cancelled) { + prompt.setVisibility(showPrompt ? VISIBLE : GONE); + confirmation.setVisibility(showPrompt ? GONE : VISIBLE); + } } - updateSecondaryText(); - updateAppSettingsLink(); }); + mExpandAnimation.start(); } @Override @@ -339,35 +377,6 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } } - private void updateSecondaryText() { - final boolean disabled = mSingleNotificationChannel != null && - getSelectedImportance() == IMPORTANCE_NONE; - if (disabled) { - mChannelDisabledView.setVisibility(View.VISIBLE); - mNumChannelsView.setVisibility(View.GONE); - } else { - mChannelDisabledView.setVisibility(View.GONE); - mNumChannelsView.setVisibility(mIsSingleDefaultChannel ? View.INVISIBLE : View.VISIBLE); - } - } - - private void updateAppSettingsLink() { - mSettingsLinkView = findViewById(R.id.app_settings); - Intent settingsIntent = getAppSettingsIntent(mPm, mPkg, mSingleNotificationChannel, - mSbn.getId(), mSbn.getTag()); - if (settingsIntent != null && getSelectedImportance() != IMPORTANCE_NONE - && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) { - mSettingsLinkView.setVisibility(View.VISIBLE); - mSettingsLinkView.setText(mContext.getString(R.string.notification_app_settings, - mSbn.getNotification().getSettingsText())); - mSettingsLinkView.setOnClickListener((View view) -> { - mAppSettingsClickListener.onClick(view, settingsIntent); - }); - } else { - mSettingsLinkView.setVisibility(View.GONE); - } - } - private Intent getAppSettingsIntent(PackageManager pm, String packageName, NotificationChannel channel, int id, String tag) { Intent intent = new Intent(Intent.ACTION_MAIN) @@ -390,6 +399,18 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G return intent; } + private void closeControls(View v) { + int[] parentLoc = new int[2]; + int[] targetLoc = new int[2]; + mGutsContainer.getLocationOnScreen(parentLoc); + v.getLocationOnScreen(targetLoc); + final int centerX = v.getWidth() / 2; + final int centerY = v.getHeight() / 2; + final int x = targetLoc[0] - parentLoc[0] + centerX; + final int y = targetLoc[1] - parentLoc[1] + centerY; + mGutsContainer.closeControls(x, y, true /* save */, false /* force */); + } + @Override public void setGutsParent(NotificationGuts guts) { mGutsContainer = guts; @@ -397,7 +418,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G @Override public boolean willBeRemoved() { - return mChannelEnabledSwitch != null && !mChannelEnabledSwitch.isChecked(); + return hasImportanceChanged(); } @Override @@ -407,9 +428,11 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G @Override public boolean handleCloseControls(boolean save, boolean force) { - if (save && hasImportanceChanged()) { + // Save regardless of the importance so we can lock the importance field if the user wants + // to keep getting notifications + if (save) { if (mCheckSaveListener != null) { - mCheckSaveListener.checkSave(() -> { saveImportance(); }); + mCheckSaveListener.checkSave(this::saveImportance, mSbn); } else { saveImportance(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java new file mode 100644 index 000000000000..0c19ec091ca6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java @@ -0,0 +1,191 @@ +/* + * 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 static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; + +import android.view.View; +import android.view.ViewGroup; + +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; + +/** + * Interface representing the entity that contains notifications. It can have + * notification views added and removed from it, and will manage displaying them to the user. + */ +public interface NotificationListContainer { + + /** + * Called when a child is being transferred. + * + * @param childTransferInProgress whether child transfer is in progress + */ + void setChildTransferInProgress(boolean childTransferInProgress); + + /** + * Change the position of child to a new location + * + * @param child the view to change the position for + * @param newIndex the new index + */ + void changeViewPosition(View child, int newIndex); + + /** + * Called when a child was added to a group. + * + * @param row row of the group child that was added + */ + void notifyGroupChildAdded(View row); + + /** + * Called when a child was removed from a group. + * + * @param row row of the child that was removed + * @param childrenContainer ViewGroup of the group that the child was removed from + */ + void notifyGroupChildRemoved(View row, ViewGroup childrenContainer); + + /** + * Generate an animation for an added child view. + * + * @param child The view to be added. + * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen. + */ + void generateAddAnimation(View child, boolean fromMoreCard); + + /** + * Generate a child order changed event. + */ + void generateChildOrderChangedEvent(); + + /** + * Returns the number of children in the NotificationListContainer. + * + * @return the number of children in the NotificationListContainer + */ + int getContainerChildCount(); + + /** + * Gets the ith child in the NotificationListContainer. + * + * @param i ith child to get + * @return the ith child in the list container + */ + View getContainerChildAt(int i); + + /** + * Remove a view from the container + * + * @param v view to remove + */ + void removeContainerView(View v); + + /** + * Add a view to the container + * + * @param v view to add + */ + void addContainerView(View v); + + /** + * Sets the maximum number of notifications to display. + * + * @param maxNotifications max number of notifications to display + */ + void setMaxDisplayedNotifications(int maxNotifications); + + /** + * Handle snapping a non-dismissable row back if the user tried to dismiss it. + * + * @param row row to snap back + */ + void snapViewIfNeeded(ExpandableNotificationRow row); + + /** + * Get the view parent for a notification entry. For example, NotificationStackScrollLayout. + * + * @param entry entry to get the view parent for + * @return the view parent for entry + */ + ViewGroup getViewParentForNotification(NotificationData.Entry entry); + + /** + * Called when the height of an expandable view changes. + * + * @param view view whose height changed + * @param animate whether this change should be animated + */ + void onHeightChanged(ExpandableView view, boolean animate); + + /** + * Resets the currently exposed menu view. + * + * @param animate whether to animate the closing/change of menu view + * @param force reset the menu view even if it looks like it is already reset + */ + void resetExposedMenuView(boolean animate, boolean force); + + /** + * Returns the NotificationSwipeActionHelper for the NotificationListContainer. + * + * @return swipe action helper for the list container + */ + NotificationSwipeActionHelper getSwipeActionHelper(); + + /** + * Called when a notification is removed from the shade. This cleans up the state for a + * given view. + * + * @param view view to clean up view state for + */ + void cleanUpViewState(View view); + + /** + * Returns whether an ExpandableNotificationRow is in a visible location or not. + * + * @param row + * @return true if row is in a visible location + */ + boolean isInVisibleLocation(ExpandableNotificationRow row); + + /** + * Sets a listener to listen for changes in notification locations. + * + * @param listener listener to set + */ + void setChildLocationsChangedListener( + NotificationLogger.OnChildLocationsChangedListener listener); + + /** + * Called when an update to the notification view hierarchy is completed. + */ + default void onNotificationViewUpdateFinished() {} + + /** + * Returns true if there are pulsing notifications. + * + * @return true if has pulsing notifications + */ + boolean hasPulsingNotifications(); + + /** + * Apply parameters of the expand animation to the layout + */ + default void applyExpandAnimationParams(ExpandAnimationParameters params) {} + + default void setExpandingNotification(ExpandableNotificationRow row) {} +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java new file mode 100644 index 000000000000..0144f4244a8f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -0,0 +1,145 @@ +/* + * 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 static com.android.systemui.statusbar.RemoteInputController.processForRemoteInput; +import static com.android.systemui.statusbar.phone.StatusBar.DEBUG; +import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_CHILD_NOTIFICATIONS; + +import android.content.ComponentName; +import android.content.Context; +import android.os.RemoteException; +import android.os.UserHandle; +import android.service.notification.StatusBarNotification; +import android.util.Log; + +import com.android.systemui.Dependency; +import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins; + +/** + * This class handles listening to notification updates and passing them along to + * NotificationPresenter to be displayed to the user. + */ +public class NotificationListener extends NotificationListenerWithPlugins { + private static final String TAG = "NotificationListener"; + + // Dependencies: + private final NotificationRemoteInputManager mRemoteInputManager = + Dependency.get(NotificationRemoteInputManager.class); + + private final Context mContext; + + protected NotificationPresenter mPresenter; + protected NotificationEntryManager mEntryManager; + + public NotificationListener(Context context) { + mContext = context; + } + + @Override + public void onListenerConnected() { + if (DEBUG) Log.d(TAG, "onListenerConnected"); + onPluginConnected(); + final StatusBarNotification[] notifications = getActiveNotifications(); + if (notifications == null) { + Log.w(TAG, "onListenerConnected unable to get active notifications."); + return; + } + final RankingMap currentRanking = getCurrentRanking(); + mPresenter.getHandler().post(() -> { + for (StatusBarNotification sbn : notifications) { + mEntryManager.addNotification(sbn, currentRanking); + } + }); + } + + @Override + public void onNotificationPosted(final StatusBarNotification sbn, + final RankingMap rankingMap) { + if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn); + if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) { + mPresenter.getHandler().post(() -> { + processForRemoteInput(sbn.getNotification(), mContext); + String key = sbn.getKey(); + mRemoteInputManager.getKeysKeptForRemoteInput().remove(key); + boolean isUpdate = + mEntryManager.getNotificationData().get(key) != null; + // In case we don't allow child notifications, we ignore children of + // notifications that have a summary, since` we're not going to show them + // anyway. This is true also when the summary is canceled, + // because children are automatically canceled by NoMan in that case. + if (!ENABLE_CHILD_NOTIFICATIONS + && mPresenter.getGroupManager().isChildInGroupWithSummary(sbn)) { + if (DEBUG) { + Log.d(TAG, "Ignoring group child due to existing summary: " + sbn); + } + + // Remove existing notification to avoid stale data. + if (isUpdate) { + mEntryManager.removeNotification(key, rankingMap); + } else { + mEntryManager.getNotificationData() + .updateRanking(rankingMap); + } + return; + } + if (isUpdate) { + mEntryManager.updateNotification(sbn, rankingMap); + } else { + mEntryManager.addNotification(sbn, rankingMap); + } + }); + } + } + + @Override + public void onNotificationRemoved(StatusBarNotification sbn, + final RankingMap rankingMap) { + if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn); + if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) { + final String key = sbn.getKey(); + mPresenter.getHandler().post(() -> { + mEntryManager.removeNotification(key, rankingMap); + }); + } + } + + @Override + public void onNotificationRankingUpdate(final RankingMap rankingMap) { + if (DEBUG) Log.d(TAG, "onRankingUpdate"); + if (rankingMap != null) { + RankingMap r = onPluginRankingUpdate(rankingMap); + mPresenter.getHandler().post(() -> { + mEntryManager.updateNotificationRanking(r); + }); + } + } + + public void setUpWithPresenter(NotificationPresenter presenter, + NotificationEntryManager entryManager) { + mPresenter = presenter; + mEntryManager = entryManager; + + try { + registerAsSystemService(mContext, + new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()), + UserHandle.USER_ALL); + } catch (RemoteException e) { + Log.e(TAG, "Unable to register notification listener", e); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java new file mode 100644 index 000000000000..47fa1d2167f0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java @@ -0,0 +1,469 @@ +/* + * 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.ActivityManager; +import android.app.Notification; +import android.app.admin.DevicePolicyManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.content.pm.UserInfo; +import android.database.ContentObserver; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.service.notification.StatusBarNotification; +import android.util.Log; +import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.Dependency; +import com.android.systemui.Dumpable; +import com.android.systemui.OverviewProxyService; +import com.android.systemui.R; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; + +/** + * Handles keeping track of the current user, profiles, and various things related to hiding + * contents, redacting notifications, and the lockscreen. + */ +public class NotificationLockscreenUserManager implements Dumpable { + private static final String TAG = "LockscreenUserManager"; + private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false; + public static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; + public static final String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION + = "com.android.systemui.statusbar.work_challenge_unlocked_notification_action"; + + private final DevicePolicyManager mDevicePolicyManager; + private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray(); + private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray(); + private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray(); + private final DeviceProvisionedController mDeviceProvisionedController = + Dependency.get(DeviceProvisionedController.class); + private final UserManager mUserManager; + private final IStatusBarService mBarService; + + private boolean mShowLockscreenNotifications; + private boolean mAllowLockscreenRemoteInput; + + protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + + if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) && + isCurrentProfile(getSendingUserId())) { + mUsersAllowingPrivateNotifications.clear(); + updateLockscreenNotificationSetting(); + mEntryManager.updateNotifications(); + } else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) { + if (userId != mCurrentUserId && isCurrentProfile(userId)) { + mPresenter.onWorkChallengeChanged(); + } + } + } + }; + + protected final BroadcastReceiver mBaseBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_USER_SWITCHED.equals(action)) { + mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + updateCurrentProfilesCache(); + Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); + + updateLockscreenNotificationSetting(); + + mPresenter.onUserSwitched(mCurrentUserId); + } else if (Intent.ACTION_USER_ADDED.equals(action)) { + updateCurrentProfilesCache(); + } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { + // Start the overview connection to the launcher service + Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser(); + } else if (Intent.ACTION_USER_PRESENT.equals(action)) { + try { + final int lastResumedActivityUserId = + ActivityManager.getService().getLastResumedActivityUserId(); + if (mUserManager.isManagedProfile(lastResumedActivityUserId)) { + showForegroundManagedProfileActivityToast(); + } + } catch (RemoteException e) { + // Abandon hope activity manager not running. + } + } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) { + final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT); + final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX); + if (intentSender != null) { + try { + mContext.startIntentSender(intentSender, null, 0, 0, 0); + } catch (IntentSender.SendIntentException e) { + /* ignore */ + } + } + if (notificationKey != null) { + try { + mBarService.onNotificationClick(notificationKey); + } catch (RemoteException e) { + /* ignore */ + } + } + } + } + }; + + protected final Context mContext; + protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>(); + + protected int mCurrentUserId = 0; + protected NotificationPresenter mPresenter; + protected NotificationEntryManager mEntryManager; + protected ContentObserver mLockscreenSettingsObserver; + protected ContentObserver mSettingsObserver; + + public NotificationLockscreenUserManager(Context context) { + mContext = context; + mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService( + Context.DEVICE_POLICY_SERVICE); + mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mCurrentUserId = ActivityManager.getCurrentUser(); + mBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + } + + public void setUpWithPresenter(NotificationPresenter presenter, + NotificationEntryManager entryManager) { + mPresenter = presenter; + mEntryManager = entryManager; + + mLockscreenSettingsObserver = new ContentObserver(mPresenter.getHandler()) { + @Override + public void onChange(boolean selfChange) { + // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or + // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ... + mUsersAllowingPrivateNotifications.clear(); + mUsersAllowingNotifications.clear(); + // ... and refresh all the notifications + updateLockscreenNotificationSetting(); + mEntryManager.updateNotifications(); + } + }; + + mSettingsObserver = new ContentObserver(mPresenter.getHandler()) { + @Override + public void onChange(boolean selfChange) { + updateLockscreenNotificationSetting(); + if (mDeviceProvisionedController.isDeviceProvisioned()) { + mEntryManager.updateNotifications(); + } + } + }; + + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false, + mLockscreenSettingsObserver, + UserHandle.USER_ALL); + + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), + true, + mLockscreenSettingsObserver, + UserHandle.USER_ALL); + + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, + mSettingsObserver); + + if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) { + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT), + false, + mSettingsObserver, + UserHandle.USER_ALL); + } + + IntentFilter allUsersFilter = new IntentFilter(); + allUsersFilter.addAction( + DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); + allUsersFilter.addAction(Intent.ACTION_DEVICE_LOCKED_CHANGED); + mContext.registerReceiverAsUser(mAllUsersReceiver, UserHandle.ALL, allUsersFilter, + null, null); + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_USER_SWITCHED); + filter.addAction(Intent.ACTION_USER_ADDED); + filter.addAction(Intent.ACTION_USER_PRESENT); + filter.addAction(Intent.ACTION_USER_UNLOCKED); + mContext.registerReceiver(mBaseBroadcastReceiver, filter); + + IntentFilter internalFilter = new IntentFilter(); + internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION); + mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null); + + updateCurrentProfilesCache(); + + mSettingsObserver.onChange(false); // set up + } + + private void showForegroundManagedProfileActivityToast() { + Toast toast = Toast.makeText(mContext, + R.string.managed_profile_foreground_toast, + Toast.LENGTH_SHORT); + TextView text = toast.getView().findViewById(android.R.id.message); + text.setCompoundDrawablesRelativeWithIntrinsicBounds( + R.drawable.stat_sys_managed_profile_status, 0, 0, 0); + int paddingPx = mContext.getResources().getDimensionPixelSize( + R.dimen.managed_profile_toast_padding); + text.setCompoundDrawablePadding(paddingPx); + toast.show(); + } + + public boolean shouldShowLockscreenNotifications() { + return mShowLockscreenNotifications; + } + + public boolean shouldAllowLockscreenRemoteInput() { + return mAllowLockscreenRemoteInput; + } + + public boolean isCurrentProfile(int userId) { + synchronized (mCurrentProfiles) { + return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null; + } + } + + /** + * Returns true if we're on a secure lockscreen and the user wants to hide notification data. + * If so, notifications should be hidden. + */ + public boolean shouldHideNotifications(int userId) { + return isLockscreenPublicMode(userId) && !userAllowsNotificationsInPublic(userId) + || (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId)); + } + + /** + * Returns true if we're on a secure lockscreen and the user wants to hide notifications via + * package-specific override. + */ + public boolean shouldHideNotifications(String key) { + if (mEntryManager == null) { + Log.wtf(TAG, "mEntryManager was null!", new Throwable()); + return true; + } + return isLockscreenPublicMode(mCurrentUserId) + && mEntryManager.getNotificationData().getVisibilityOverride(key) == + Notification.VISIBILITY_SECRET; + } + + public boolean shouldShowOnKeyguard(StatusBarNotification sbn) { + if (mEntryManager == null) { + Log.wtf(TAG, "mEntryManager was null!", new Throwable()); + return false; + } + return mShowLockscreenNotifications + && !mEntryManager.getNotificationData().isAmbient(sbn.getKey()); + } + + private void setShowLockscreenNotifications(boolean show) { + mShowLockscreenNotifications = show; + } + + private void setLockscreenAllowRemoteInput(boolean allowLockscreenRemoteInput) { + mAllowLockscreenRemoteInput = allowLockscreenRemoteInput; + } + + protected void updateLockscreenNotificationSetting() { + final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, + 1, + mCurrentUserId) != 0; + final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( + null /* admin */, mCurrentUserId); + final boolean allowedByDpm = (dpmFlags + & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; + + setShowLockscreenNotifications(show && allowedByDpm); + + if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) { + final boolean remoteInput = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, + 0, + mCurrentUserId) != 0; + final boolean remoteInputDpm = + (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0; + + setLockscreenAllowRemoteInput(remoteInput && remoteInputDpm); + } else { + setLockscreenAllowRemoteInput(false); + } + } + + /** + * Has the given user chosen to allow their private (full) notifications to be shown even + * when the lockscreen is in "public" (secure & locked) mode? + */ + public boolean userAllowsPrivateNotificationsInPublic(int userHandle) { + if (userHandle == UserHandle.USER_ALL) { + return true; + } + + if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { + final boolean allowedByUser = 0 != Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); + final boolean allowedByDpm = adminAllowsUnredactedNotifications(userHandle); + final boolean allowed = allowedByUser && allowedByDpm; + mUsersAllowingPrivateNotifications.append(userHandle, allowed); + return allowed; + } + + return mUsersAllowingPrivateNotifications.get(userHandle); + } + + private boolean adminAllowsUnredactedNotifications(int userHandle) { + if (userHandle == UserHandle.USER_ALL) { + return true; + } + final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */, + userHandle); + return (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0; + } + + /** + * Save the current "public" (locked and secure) state of the lockscreen. + */ + public void setLockscreenPublicMode(boolean publicMode, int userId) { + mLockscreenPublicMode.put(userId, publicMode); + } + + public boolean isLockscreenPublicMode(int userId) { + if (userId == UserHandle.USER_ALL) { + return mLockscreenPublicMode.get(mCurrentUserId, false); + } + return mLockscreenPublicMode.get(userId, false); + } + + /** + * Has the given user chosen to allow notifications to be shown even when the lockscreen is in + * "public" (secure & locked) mode? + */ + private boolean userAllowsNotificationsInPublic(int userHandle) { + if (userHandle == UserHandle.USER_ALL) { + return true; + } + + if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) { + final boolean allowed = 0 != Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle); + mUsersAllowingNotifications.append(userHandle, allowed); + return allowed; + } + + return mUsersAllowingNotifications.get(userHandle); + } + + /** @return true if the entry needs redaction when on the lockscreen. */ + public boolean needsRedaction(NotificationData.Entry ent) { + int userId = ent.notification.getUserId(); + + boolean currentUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(mCurrentUserId); + boolean notiUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(userId); + boolean redactedLockscreen = currentUserWantsRedaction || notiUserWantsRedaction; + + boolean notificationRequestsRedaction = + ent.notification.getNotification().visibility == Notification.VISIBILITY_PRIVATE; + boolean userForcesRedaction = packageHasVisibilityOverride(ent.notification.getKey()); + + return userForcesRedaction || notificationRequestsRedaction && redactedLockscreen; + } + + private boolean packageHasVisibilityOverride(String key) { + if (mEntryManager == null) { + Log.wtf(TAG, "mEntryManager was null!", new Throwable()); + return true; + } + return mEntryManager.getNotificationData().getVisibilityOverride(key) == + Notification.VISIBILITY_PRIVATE; + } + + + private void updateCurrentProfilesCache() { + synchronized (mCurrentProfiles) { + mCurrentProfiles.clear(); + if (mUserManager != null) { + for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) { + mCurrentProfiles.put(user.id, user); + } + } + } + } + + public boolean isAnyProfilePublicMode() { + for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) { + if (isLockscreenPublicMode(mCurrentProfiles.valueAt(i).id)) { + return true; + } + } + return false; + } + + /** + * Returns the current user id. This can change if the user is switched. + */ + public int getCurrentUserId() { + return mCurrentUserId; + } + + public SparseArray<UserInfo> getCurrentProfiles() { + return mCurrentProfiles; + } + + public void destroy() { + mContext.unregisterReceiver(mBaseBroadcastReceiver); + mContext.unregisterReceiver(mAllUsersReceiver); + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("NotificationLockscreenUserManager state:"); + pw.print(" mCurrentUserId="); + pw.println(mCurrentUserId); + pw.print(" mShowLockscreenNotifications="); + pw.println(mShowLockscreenNotifications); + pw.print(" mAllowLockscreenRemoteInput="); + pw.println(mAllowLockscreenRemoteInput); + pw.print(" mCurrentProfiles="); + for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) { + final int userId = mCurrentProfiles.valueAt(i).id; + pw.print("" + userId + " "); + } + pw.println(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java new file mode 100644 index 000000000000..4225f83c5b11 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java @@ -0,0 +1,227 @@ +/* + * 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.content.Context; +import android.os.Handler; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.service.notification.NotificationListenerService; +import android.util.ArraySet; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.Dependency; +import com.android.systemui.UiOffloadThread; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +/** + * Handles notification logging, in particular, logging which notifications are visible and which + * are not. + */ +public class NotificationLogger { + private static final String TAG = "NotificationLogger"; + + /** The minimum delay in ms between reports of notification visibility. */ + private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500; + + /** Keys of notifications currently visible to the user. */ + private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications = + new ArraySet<>(); + + // Dependencies: + private final NotificationListenerService mNotificationListener = + Dependency.get(NotificationListener.class); + private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class); + + protected NotificationEntryManager mEntryManager; + protected Handler mHandler = new Handler(); + protected IStatusBarService mBarService; + private long mLastVisibilityReportUptimeMs; + private NotificationListContainer mListContainer; + + protected final OnChildLocationsChangedListener mNotificationLocationsChangedListener = + new OnChildLocationsChangedListener() { + @Override + public void onChildLocationsChanged() { + if (mHandler.hasCallbacks(mVisibilityReporter)) { + // Visibilities will be reported when the existing + // callback is executed. + return; + } + // Calculate when we're allowed to run the visibility + // reporter. Note that this timestamp might already have + // passed. That's OK, the callback will just be executed + // ASAP. + long nextReportUptimeMs = + mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS; + mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs); + } + }; + + // Tracks notifications currently visible in mNotificationStackScroller and + // emits visibility events via NoMan on changes. + protected final Runnable mVisibilityReporter = new Runnable() { + private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications = + new ArraySet<>(); + private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications = + new ArraySet<>(); + private final ArraySet<NotificationVisibility> mTmpNoLongerVisibleNotifications = + new ArraySet<>(); + + @Override + public void run() { + mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis(); + + // 1. Loop over mNotificationData entries: + // A. Keep list of visible notifications. + // B. Keep list of previously hidden, now visible notifications. + // 2. Compute no-longer visible notifications by removing currently + // visible notifications from the set of previously visible + // notifications. + // 3. Report newly visible and no-longer visible notifications. + // 4. Keep currently visible notifications for next report. + ArrayList<NotificationData.Entry> activeNotifications = mEntryManager + .getNotificationData().getActiveNotifications(); + int N = activeNotifications.size(); + for (int i = 0; i < N; i++) { + NotificationData.Entry entry = activeNotifications.get(i); + String key = entry.notification.getKey(); + boolean isVisible = mListContainer.isInVisibleLocation(entry.row); + NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible); + boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj); + if (isVisible) { + // Build new set of visible notifications. + mTmpCurrentlyVisibleNotifications.add(visObj); + if (!previouslyVisible) { + mTmpNewlyVisibleNotifications.add(visObj); + } + } else { + // release object + visObj.recycle(); + } + } + mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications); + mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications); + + logNotificationVisibilityChanges( + mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications); + + recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); + mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications); + + recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications); + mTmpCurrentlyVisibleNotifications.clear(); + mTmpNewlyVisibleNotifications.clear(); + mTmpNoLongerVisibleNotifications.clear(); + } + }; + + public NotificationLogger() { + mBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + } + + public void setUpWithEntryManager(NotificationEntryManager entryManager, + NotificationListContainer listContainer) { + mEntryManager = entryManager; + mListContainer = listContainer; + } + + public void stopNotificationLogging() { + // Report all notifications as invisible and turn down the + // reporter. + if (!mCurrentlyVisibleNotifications.isEmpty()) { + logNotificationVisibilityChanges( + Collections.emptyList(), mCurrentlyVisibleNotifications); + recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); + } + mHandler.removeCallbacks(mVisibilityReporter); + mListContainer.setChildLocationsChangedListener(null); + } + + public void startNotificationLogging() { + mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener); + // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't + // cause the scroller to emit child location events. Hence generate + // one ourselves to guarantee that we're reporting visible + // notifications. + // (Note that in cases where the scroller does emit events, this + // additional event doesn't break anything.) + mNotificationLocationsChangedListener.onChildLocationsChanged(); + } + + private void logNotificationVisibilityChanges( + Collection<NotificationVisibility> newlyVisible, + Collection<NotificationVisibility> noLongerVisible) { + if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) { + return; + } + NotificationVisibility[] newlyVisibleAr = + newlyVisible.toArray(new NotificationVisibility[newlyVisible.size()]); + NotificationVisibility[] noLongerVisibleAr = + noLongerVisible.toArray(new NotificationVisibility[noLongerVisible.size()]); + mUiOffloadThread.submit(() -> { + try { + mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr); + } catch (RemoteException e) { + // Ignore. + } + + final int N = newlyVisible.size(); + if (N > 0) { + String[] newlyVisibleKeyAr = new String[N]; + for (int i = 0; i < N; i++) { + newlyVisibleKeyAr[i] = newlyVisibleAr[i].key; + } + + // TODO: Call NotificationEntryManager to do this, once it exists. + // TODO: Consider not catching all runtime exceptions here. + try { + mNotificationListener.setNotificationsShown(newlyVisibleKeyAr); + } catch (RuntimeException e) { + Log.d(TAG, "failed setNotificationsShown: ", e); + } + } + }); + } + + private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) { + final int N = array.size(); + for (int i = 0 ; i < N; i++) { + array.valueAt(i).recycle(); + } + array.clear(); + } + + @VisibleForTesting + public Runnable getVisibilityReporter() { + return mVisibilityReporter; + } + + /** + * A listener that is notified when some child locations might have changed. + */ + public interface OnChildLocationsChangedListener { + void onChildLocationsChanged(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java new file mode 100644 index 000000000000..852239a2143b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -0,0 +1,267 @@ +/* + * 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.Notification; +import android.content.Context; +import android.media.MediaMetadata; +import android.media.session.MediaController; +import android.media.session.MediaSession; +import android.media.session.MediaSessionManager; +import android.media.session.PlaybackState; +import android.os.UserHandle; +import android.util.Log; + +import com.android.systemui.Dumpable; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Handles tasks and state related to media notifications. For example, there is a 'current' media + * notification, which this class keeps track of. + */ +public class NotificationMediaManager implements Dumpable { + private static final String TAG = "NotificationMediaManager"; + public static final boolean DEBUG_MEDIA = false; + + private final Context mContext; + private final MediaSessionManager mMediaSessionManager; + + protected NotificationPresenter mPresenter; + protected NotificationEntryManager mEntryManager; + private MediaController mMediaController; + private String mMediaNotificationKey; + private MediaMetadata mMediaMetadata; + + private final MediaController.Callback mMediaListener = new MediaController.Callback() { + @Override + public void onPlaybackStateChanged(PlaybackState state) { + super.onPlaybackStateChanged(state); + if (DEBUG_MEDIA) { + Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state); + } + if (state != null) { + if (!isPlaybackActive(state.getState())) { + clearCurrentMediaNotification(); + mPresenter.updateMediaMetaData(true, true); + } + } + } + + @Override + public void onMetadataChanged(MediaMetadata metadata) { + super.onMetadataChanged(metadata); + if (DEBUG_MEDIA) { + Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); + } + mMediaMetadata = metadata; + mPresenter.updateMediaMetaData(true, true); + } + }; + + public NotificationMediaManager(Context context) { + mContext = context; + mMediaSessionManager + = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); + // TODO: use MediaSessionManager.SessionListener to hook us up to future updates + // in session state + } + + public void setUpWithPresenter(NotificationPresenter presenter, + NotificationEntryManager entryManager) { + mPresenter = presenter; + mEntryManager = entryManager; + } + + public void onNotificationRemoved(String key) { + if (key.equals(mMediaNotificationKey)) { + clearCurrentMediaNotification(); + mPresenter.updateMediaMetaData(true, true); + } + } + + public String getMediaNotificationKey() { + return mMediaNotificationKey; + } + + public MediaMetadata getMediaMetadata() { + return mMediaMetadata; + } + + public void findAndUpdateMediaNotifications() { + boolean metaDataChanged = false; + + synchronized (mEntryManager.getNotificationData()) { + ArrayList<NotificationData.Entry> activeNotifications = mEntryManager + .getNotificationData().getActiveNotifications(); + final int N = activeNotifications.size(); + + // Promote the media notification with a controller in 'playing' state, if any. + NotificationData.Entry mediaNotification = null; + MediaController controller = null; + for (int i = 0; i < N; i++) { + final NotificationData.Entry entry = activeNotifications.get(i); + + if (isMediaNotification(entry)) { + final MediaSession.Token token = + entry.notification.getNotification().extras.getParcelable( + Notification.EXTRA_MEDIA_SESSION); + if (token != null) { + MediaController aController = new MediaController(mContext, token); + if (PlaybackState.STATE_PLAYING == + getMediaControllerPlaybackState(aController)) { + if (DEBUG_MEDIA) { + Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching " + + entry.notification.getKey()); + } + mediaNotification = entry; + controller = aController; + break; + } + } + } + } + if (mediaNotification == null) { + // Still nothing? OK, let's just look for live media sessions and see if they match + // one of our notifications. This will catch apps that aren't (yet!) using media + // notifications. + + if (mMediaSessionManager != null) { + // TODO: Should this really be for all users? + final List<MediaController> sessions + = mMediaSessionManager.getActiveSessionsForUser( + null, + UserHandle.USER_ALL); + + for (MediaController aController : sessions) { + if (PlaybackState.STATE_PLAYING == + getMediaControllerPlaybackState(aController)) { + // now to see if we have one like this + final String pkg = aController.getPackageName(); + + for (int i = 0; i < N; i++) { + final NotificationData.Entry entry = activeNotifications.get(i); + if (entry.notification.getPackageName().equals(pkg)) { + if (DEBUG_MEDIA) { + Log.v(TAG, "DEBUG_MEDIA: found controller matching " + + entry.notification.getKey()); + } + controller = aController; + mediaNotification = entry; + break; + } + } + } + } + } + } + + if (controller != null && !sameSessions(mMediaController, controller)) { + // We have a new media session + clearCurrentMediaNotification(); + mMediaController = controller; + mMediaController.registerCallback(mMediaListener); + mMediaMetadata = mMediaController.getMetadata(); + if (DEBUG_MEDIA) { + Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: " + + mMediaMetadata); + } + + if (mediaNotification != null) { + mMediaNotificationKey = mediaNotification.notification.getKey(); + if (DEBUG_MEDIA) { + Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key=" + + mMediaNotificationKey + " controller=" + mMediaController); + } + } + metaDataChanged = true; + } + } + + if (metaDataChanged) { + mEntryManager.updateNotifications(); + } + mPresenter.updateMediaMetaData(metaDataChanged, true); + } + + public void clearCurrentMediaNotification() { + mMediaNotificationKey = null; + mMediaMetadata = null; + if (mMediaController != null) { + if (DEBUG_MEDIA) { + Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: " + + mMediaController.getPackageName()); + } + mMediaController.unregisterCallback(mMediaListener); + } + mMediaController = null; + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.print(" mMediaSessionManager="); + pw.println(mMediaSessionManager); + pw.print(" mMediaNotificationKey="); + pw.println(mMediaNotificationKey); + pw.print(" mMediaController="); + pw.print(mMediaController); + if (mMediaController != null) { + pw.print(" state=" + mMediaController.getPlaybackState()); + } + pw.println(); + pw.print(" mMediaMetadata="); + pw.print(mMediaMetadata); + if (mMediaMetadata != null) { + pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE)); + } + pw.println(); + } + + private boolean isPlaybackActive(int state) { + return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR + && state != PlaybackState.STATE_NONE; + } + + private boolean sameSessions(MediaController a, MediaController b) { + if (a == b) { + return true; + } + if (a == null) { + return false; + } + return a.controlsSameSession(b); + } + + private int getMediaControllerPlaybackState(MediaController controller) { + if (controller != null) { + final PlaybackState playbackState = controller.getPlaybackState(); + if (playbackState != null) { + return playbackState.getState(); + } + } + return PlaybackState.STATE_NONE; + } + + private boolean isMediaNotification(NotificationData.Entry entry) { + // TODO: confirm that there's a valid media key + return entry.getExpandedContentView() != null && + entry.getExpandedContentView() + .findViewById(com.android.internal.R.id.media_actions) != null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java index 99b4b0799cba..037eeb2d298b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java @@ -88,6 +88,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl private float mHorizSpaceForIcon = -1; private int mVertSpaceForIcons = -1; private int mIconPadding = -1; + private int mSidePadding; private float mAlpha = 0f; private float mPrevX; @@ -175,7 +176,6 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl final Resources res = mContext.getResources(); mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size); mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height); - mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding); mMenuItems.clear(); // Construct the menu items based on the notification if (mParent != null && mParent.getStatusBarNotification() != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java new file mode 100644 index 000000000000..5263bb4173a3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java @@ -0,0 +1,120 @@ +/* + * 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.content.Intent; +import android.os.Handler; +import android.view.View; + +/** + * An abstraction of something that presents notifications, e.g. StatusBar. Contains methods + * for both querying the state of the system (some modularised piece of functionality may + * want to act differently based on e.g. whether the presenter is visible to the user or not) and + * for affecting the state of the system (e.g. starting an intent, given that the presenter may + * want to perform some action before doing so). + */ +public interface NotificationPresenter extends NotificationData.Environment, + NotificationRemoteInputManager.Callback, + ExpandableNotificationRow.OnExpandClickListener, + ActivatableNotificationView.OnActivatedListener, + NotificationEntryManager.Callback { + /** + * Returns true if the presenter is not visible. For example, it may not be necessary to do + * animations if this returns true. + */ + boolean isPresenterFullyCollapsed(); + + /** + * Returns true if the presenter is locked. For example, if the keyguard is active. + */ + boolean isPresenterLocked(); + + /** + * Runs the given intent. The presenter may want to run some animations or close itself when + * this happens. + */ + void startNotificationGutsIntent(Intent intent, int appUid, ExpandableNotificationRow row); + + /** + * Returns the Handler for NotificationPresenter. + */ + Handler getHandler(); + + /** + * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper. + */ + void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation); + + /** + * Called when the locked status of the device is changed for a work profile. + */ + void onWorkChallengeChanged(); + + /** + * Called when the current user changes. + * @param newUserId new user id + */ + void onUserSwitched(int newUserId); + + /** + * Gets the NotificationLockscreenUserManager for this Presenter. + */ + NotificationLockscreenUserManager getNotificationLockscreenUserManager(); + + /** + * Wakes the device up if dozing. + * + * @param time the time when the request to wake up was issued + * @param where which view caused this wake up request + */ + void wakeUpIfDozing(long time, View where); + + /** + * True if the device currently requires a PIN, pattern, or password to unlock. + * + * @param userId user id to query about + * @return true iff the device is locked + */ + boolean isDeviceLocked(int userId); + + /** + * @return true iff the device is in vr mode + */ + boolean isDeviceInVrMode(); + + /** + * Updates the visual representation of the notifications. + */ + void updateNotificationViews(); + + /** + * @return true iff the device is dozing + */ + boolean isDozing(); + + /** + * Returns the maximum number of notifications to show while locked. + * + * @param recompute whether something has changed that means we should recompute this value + * @return the maximum number of notifications to show while locked + */ + int getMaxNotificationsWhileLocked(boolean recompute); + + /** + * Called when the row states are updated by NotificationViewHierarchyManager. + */ + void onUpdateRowStates(); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java new file mode 100644 index 000000000000..3c480d80dea8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -0,0 +1,451 @@ +/* + * 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 static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; + +import android.app.ActivityManager; +import android.app.PendingIntent; +import android.app.RemoteInput; +import android.content.Context; +import android.content.Intent; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserManager; +import android.service.notification.StatusBarNotification; +import android.util.ArraySet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.RemoteViews; +import android.widget.TextView; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.Dependency; +import com.android.systemui.Dumpable; +import com.android.systemui.statusbar.policy.RemoteInputView; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Set; + +/** + * Class for handling remote input state over a set of notifications. This class handles things + * like keeping notifications temporarily that were cancelled as a response to a remote input + * interaction, keeping track of notifications to remove when NotificationPresenter is collapsed, + * and handling clicks on remote views. + */ +public class NotificationRemoteInputManager implements Dumpable { + public static final boolean ENABLE_REMOTE_INPUT = + SystemProperties.getBoolean("debug.enable_remote_input", true); + public static final boolean FORCE_REMOTE_INPUT_HISTORY = + SystemProperties.getBoolean("debug.force_remoteinput_history", true); + private static final boolean DEBUG = false; + private static final String TAG = "NotificationRemoteInputManager"; + + /** + * How long to wait before auto-dismissing a notification that was kept for remote input, and + * has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel + * these given that they technically don't exist anymore. We wait a bit in case the app issues + * an update. + */ + private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200; + + protected final ArraySet<NotificationData.Entry> mRemoteInputEntriesToRemoveOnCollapse = + new ArraySet<>(); + + // Dependencies: + protected final NotificationLockscreenUserManager mLockscreenUserManager = + Dependency.get(NotificationLockscreenUserManager.class); + + /** + * Notifications with keys in this set are not actually around anymore. We kept them around + * when they were canceled in response to a remote input interaction. This allows us to show + * what you replied and allows you to continue typing into it. + */ + protected final ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>(); + protected final Context mContext; + private final UserManager mUserManager; + + protected RemoteInputController mRemoteInputController; + protected NotificationPresenter mPresenter; + protected NotificationEntryManager mEntryManager; + protected IStatusBarService mBarService; + protected Callback mCallback; + + private final RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { + + @Override + public boolean onClickHandler( + final View view, final PendingIntent pendingIntent, final Intent fillInIntent) { + mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), view); + + if (handleRemoteInput(view, pendingIntent)) { + return true; + } + + if (DEBUG) { + Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); + } + logActionClick(view); + // The intent we are sending is for the application, which + // won't have permission to immediately start an activity after + // the user switches to home. We know it is safe to do at this + // point, so make sure new activity switches are now allowed. + try { + ActivityManager.getService().resumeAppSwitches(); + } catch (RemoteException e) { + } + return mCallback.handleRemoteViewClick(view, pendingIntent, fillInIntent, + () -> superOnClickHandler(view, pendingIntent, fillInIntent)); + } + + private void logActionClick(View view) { + ViewParent parent = view.getParent(); + String key = getNotificationKeyForParent(parent); + if (key == null) { + Log.w(TAG, "Couldn't determine notification for click."); + return; + } + int index = -1; + // If this is a default template, determine the index of the button. + if (view.getId() == com.android.internal.R.id.action0 && + parent != null && parent instanceof ViewGroup) { + ViewGroup actionGroup = (ViewGroup) parent; + index = actionGroup.indexOfChild(view); + } + try { + mBarService.onNotificationActionClick(key, index); + } catch (RemoteException e) { + // Ignore + } + } + + private String getNotificationKeyForParent(ViewParent parent) { + while (parent != null) { + if (parent instanceof ExpandableNotificationRow) { + return ((ExpandableNotificationRow) parent) + .getStatusBarNotification().getKey(); + } + parent = parent.getParent(); + } + return null; + } + + private boolean superOnClickHandler(View view, PendingIntent pendingIntent, + Intent fillInIntent) { + return super.onClickHandler(view, pendingIntent, fillInIntent, + WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY); + } + + private boolean handleRemoteInput(View view, PendingIntent pendingIntent) { + if (mCallback.shouldHandleRemoteInput(view, pendingIntent)) { + return true; + } + + Object tag = view.getTag(com.android.internal.R.id.remote_input_tag); + RemoteInput[] inputs = null; + if (tag instanceof RemoteInput[]) { + inputs = (RemoteInput[]) tag; + } + + if (inputs == null) { + return false; + } + + RemoteInput input = null; + + for (RemoteInput i : inputs) { + if (i.getAllowFreeFormInput()) { + input = i; + } + } + + if (input == null) { + return false; + } + + ViewParent p = view.getParent(); + RemoteInputView riv = null; + while (p != null) { + if (p instanceof View) { + View pv = (View) p; + if (pv.isRootNamespace()) { + riv = findRemoteInputView(pv); + break; + } + } + p = p.getParent(); + } + ExpandableNotificationRow row = null; + while (p != null) { + if (p instanceof ExpandableNotificationRow) { + row = (ExpandableNotificationRow) p; + break; + } + p = p.getParent(); + } + + if (row == null) { + return false; + } + + row.setUserExpanded(true); + + if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) { + final int userId = pendingIntent.getCreatorUserHandle().getIdentifier(); + if (mLockscreenUserManager.isLockscreenPublicMode(userId)) { + mCallback.onLockedRemoteInput(row, view); + return true; + } + if (mUserManager.getUserInfo(userId).isManagedProfile() + && mPresenter.isDeviceLocked(userId)) { + mCallback.onLockedWorkRemoteInput(userId, row, view); + return true; + } + } + + if (riv == null) { + riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild()); + if (riv == null) { + return false; + } + if (!row.getPrivateLayout().getExpandedChild().isShown()) { + mCallback.onMakeExpandedVisibleForRemoteInput(row, view); + return true; + } + } + + int width = view.getWidth(); + if (view instanceof TextView) { + // Center the reveal on the text which might be off-center from the TextView + TextView tv = (TextView) view; + if (tv.getLayout() != null) { + int innerWidth = (int) tv.getLayout().getLineWidth(0); + innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight(); + width = Math.min(width, innerWidth); + } + } + int cx = view.getLeft() + width / 2; + int cy = view.getTop() + view.getHeight() / 2; + int w = riv.getWidth(); + int h = riv.getHeight(); + int r = Math.max( + Math.max(cx + cy, cx + (h - cy)), + Math.max((w - cx) + cy, (w - cx) + (h - cy))); + + riv.setRevealParameters(cx, cy, r); + riv.setPendingIntent(pendingIntent); + riv.setRemoteInput(inputs, input); + riv.focusAnimated(); + + return true; + } + + private RemoteInputView findRemoteInputView(View v) { + if (v == null) { + return null; + } + return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG); + } + }; + + public NotificationRemoteInputManager(Context context) { + mContext = context; + mBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + } + + public void setUpWithPresenter(NotificationPresenter presenter, + NotificationEntryManager entryManager, + Callback callback, + RemoteInputController.Delegate delegate) { + mPresenter = presenter; + mEntryManager = entryManager; + mCallback = callback; + mRemoteInputController = new RemoteInputController(delegate); + mRemoteInputController.addCallback(new RemoteInputController.Callback() { + @Override + public void onRemoteInputSent(NotificationData.Entry entry) { + if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) { + mEntryManager.removeNotification(entry.key, null); + } else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) { + // We're currently holding onto this notification, but from the apps point of + // view it is already canceled, so we'll need to cancel it on the apps behalf + // after sending - unless the app posts an update in the mean time, so wait a + // bit. + mPresenter.getHandler().postDelayed(() -> { + if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) { + mEntryManager.removeNotification(entry.key, null); + } + }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY); + } + try { + mBarService.onNotificationDirectReplied(entry.notification.getKey()); + } catch (RemoteException e) { + // Nothing to do, system going down + } + } + }); + + } + + public RemoteInputController getController() { + return mRemoteInputController; + } + + public void onUpdateNotification(NotificationData.Entry entry) { + mRemoteInputEntriesToRemoveOnCollapse.remove(entry); + } + + /** + * Returns true if NotificationRemoteInputManager wants to keep this notification around. + * + * @param entry notification being removed + */ + public boolean onRemoveNotification(NotificationData.Entry entry) { + if (entry != null && mRemoteInputController.isRemoteInputActive(entry) + && (entry.row != null && !entry.row.isDismissed())) { + mRemoteInputEntriesToRemoveOnCollapse.add(entry); + return true; + } + return false; + } + + public void onPerformRemoveNotification(StatusBarNotification n, + NotificationData.Entry entry) { + if (mRemoteInputController.isRemoteInputActive(entry)) { + mRemoteInputController.removeRemoteInput(entry, null); + } + if (FORCE_REMOTE_INPUT_HISTORY + && mKeysKeptForRemoteInput.contains(n.getKey())) { + mKeysKeptForRemoteInput.remove(n.getKey()); + } + } + + public void removeRemoteInputEntriesKeptUntilCollapsed() { + for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) { + NotificationData.Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i); + mRemoteInputController.removeRemoteInput(entry, null); + mEntryManager.removeNotification(entry.key, mEntryManager.getLatestRankingMap()); + } + mRemoteInputEntriesToRemoveOnCollapse.clear(); + } + + public void checkRemoteInputOutside(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar + && event.getX() == 0 && event.getY() == 0 // a touch outside both bars + && mRemoteInputController.isRemoteInputActive()) { + mRemoteInputController.closeRemoteInputs(); + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("NotificationRemoteInputManager state:"); + pw.print(" mRemoteInputEntriesToRemoveOnCollapse: "); + pw.println(mRemoteInputEntriesToRemoveOnCollapse); + pw.print(" mKeysKeptForRemoteInput: "); + pw.println(mKeysKeptForRemoteInput); + } + + public void bindRow(ExpandableNotificationRow row) { + row.setRemoteInputController(mRemoteInputController); + row.setRemoteViewClickHandler(mOnClickHandler); + } + + public Set<String> getKeysKeptForRemoteInput() { + return mKeysKeptForRemoteInput; + } + + @VisibleForTesting + public Set<NotificationData.Entry> getRemoteInputEntriesToRemoveOnCollapse() { + return mRemoteInputEntriesToRemoveOnCollapse; + } + + /** + * Callback for various remote input related events, or for providing information that + * NotificationRemoteInputManager needs to know to decide what to do. + */ + public interface Callback { + + /** + * Called when remote input was activated but the device is locked. + * + * @param row + * @param clicked + */ + void onLockedRemoteInput(ExpandableNotificationRow row, View clicked); + + /** + * Called when remote input was activated but the device is locked and in a managed profile. + * + * @param userId + * @param row + * @param clicked + */ + void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row, View clicked); + + /** + * Called when a row should be made expanded for the purposes of remote input. + * + * @param row + * @param clickedView + */ + void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row, View clickedView); + + /** + * Return whether or not remote input should be handled for this view. + * + * @param view + * @param pendingIntent + * @return true iff the remote input should be handled + */ + boolean shouldHandleRemoteInput(View view, PendingIntent pendingIntent); + + /** + * Performs any special handling for a remote view click. The default behaviour can be + * called through the defaultHandler parameter. + * + * @param view + * @param pendingIntent + * @param fillInIntent + * @param defaultHandler + * @return true iff the click was handled + */ + boolean handleRemoteViewClick(View view, PendingIntent pendingIntent, Intent fillInIntent, + ClickHandler defaultHandler); + } + + /** + * Helper interface meant for passing the default on click behaviour to NotificationPresenter, + * so it may do its own handling before invoking the default behaviour. + */ + public interface ClickHandler { + /** + * Tries to handle a click on a remote view. + * + * @return true iff the click was handled + */ + boolean handleClick(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 5557dde7a5d6..cad956cd602a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -25,6 +25,7 @@ import android.content.res.Resources; import android.graphics.Rect; import android.os.SystemProperties; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -56,6 +57,7 @@ public class NotificationShelf extends ActivatableNotificationView implements private static final boolean ICON_ANMATIONS_WHILE_SCROLLING = SystemProperties.getBoolean("debug.icon_scroll_animations", true); private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag; + private static final String TAG = "NotificationShelf"; private ViewInvertHelper mViewInvertHelper; private boolean mDark; private NotificationIconContainer mShelfIcons; @@ -82,9 +84,7 @@ public class NotificationShelf extends ActivatableNotificationView implements private boolean mNoAnimationsInThisFrame; private boolean mAnimationsEnabled = true; private boolean mShowNotificationShelf; - private boolean mVibrationOnAnimation; - private boolean mUserTouchingScreen; - private boolean mTouchActive; + private float mFirstElementRoundness; public NotificationShelf(Context context, AttributeSet attrs) { super(context, attrs); @@ -100,25 +100,14 @@ public class NotificationShelf extends ActivatableNotificationView implements setClipToActualHeight(false); setClipChildren(false); setClipToPadding(false); - mShelfIcons.setShowAllIcons(false); - mVibrationOnAnimation = mContext.getResources().getBoolean( - R.bool.config_vibrateOnIconAnimation); - updateVibrationOnAnimation(); + mShelfIcons.setIsStaticLayout(false); mViewInvertHelper = new ViewInvertHelper(mShelfIcons, NotificationPanelView.DOZE_ANIMATION_DURATION); mShelfState = new ShelfState(); + setBottomRoundness(1.0f, false /* animate */); initDimens(); } - private void updateVibrationOnAnimation() { - mShelfIcons.setVibrateOnAnimation(mVibrationOnAnimation && mTouchActive); - } - - public void setTouchActive(boolean touchActive) { - mTouchActive = touchActive; - updateVibrationOnAnimation(); - } - public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) { mAmbientState = ambientState; mHostLayout = hostLayout; @@ -252,6 +241,8 @@ public class NotificationShelf extends ActivatableNotificationView implements boolean expandingAnimated = mAmbientState.isExpansionChanging() && !mAmbientState.isPanelTracking(); int baseZHeight = mAmbientState.getBaseZHeight(); + int backgroundTop = 0; + float firstElementRoundness = 0.0f; while (notificationIndex < mHostLayout.getChildCount()) { ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex); notificationIndex++; @@ -302,9 +293,25 @@ public class NotificationShelf extends ActivatableNotificationView implements if (notGoneIndex != 0 || !aboveShelf) { row.setAboveShelf(false); } + if (notGoneIndex == 0) { + StatusBarIconView icon = row.getEntry().expandedIcon; + NotificationIconContainer.IconState iconState = getIconState(icon); + if (iconState != null && iconState.clampedAppearAmount == 1.0f) { + // only if the first icon is fully in the shelf we want to clip to it! + backgroundTop = (int) (row.getTranslationY() - getTranslationY()); + firstElementRoundness = row.getCurrentTopRoundness(); + } else if (iconState == null) { + Log.wtf(TAG, "iconState is null. ExpandedIcon: " + row.getEntry().expandedIcon + + (row.getEntry().expandedIcon != null + ? "\n icon parent: " + row.getEntry().expandedIcon.getParent() : "") + + " \n number of notifications: " + mHostLayout.getChildCount() ); + } + } notGoneIndex++; previousColor = ownColorUntinted; } + setBackgroundTop(backgroundTop); + setFirstElementRoundness(firstElementRoundness); mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex()); mShelfIcons.calculateIconTranslations(); mShelfIcons.applyIconStates(); @@ -325,6 +332,13 @@ public class NotificationShelf extends ActivatableNotificationView implements } } + private void setFirstElementRoundness(float firstElementRoundness) { + if (mFirstElementRoundness != firstElementRoundness) { + mFirstElementRoundness = firstElementRoundness; + setTopRoundness(firstElementRoundness, false /* animate */); + } + } + private void updateIconClipAmount(ExpandableNotificationRow row) { float maxTop = row.getTranslationY(); StatusBarIconView icon = row.getEntry().expandedIcon; @@ -674,7 +688,8 @@ public class NotificationShelf extends ActivatableNotificationView implements if (isLayoutRtl()) { start = getWidth() - start - mCollapsedIcons.getWidth(); } - int width = (int) NotificationUtils.interpolate(start + mCollapsedIcons.getWidth(), + int width = (int) NotificationUtils.interpolate( + start + mCollapsedIcons.getFinalTranslationX(), mShelfIcons.getWidth(), openedAmount); mShelfIcons.setActualLayoutWidth(width); @@ -684,6 +699,9 @@ public class NotificationShelf extends ActivatableNotificationView implements // we have to ensure that adding the low priority notification won't lead to an // overflow collapsedPadding -= (1.0f + OVERFLOW_EARLY_AMOUNT) * mCollapsedIcons.getIconSize(); + } else { + // Partial overflow padding will fill enough space to add extra dots + collapsedPadding -= mCollapsedIcons.getPartialOverflowExtraPadding(); } float padding = NotificationUtils.interpolate(collapsedPadding, mShelfIcons.getPaddingEnd(), @@ -693,7 +711,6 @@ public class NotificationShelf extends ActivatableNotificationView implements mShelfIcons.getPaddingStart(), openedAmount); mShelfIcons.setActualPaddingStart(paddingStart); mShelfIcons.setOpenedAmount(openedAmount); - mShelfIcons.setVisualOverflowAdaption(mCollapsedIcons.getVisualOverflowAdaption()); } public void setMaxLayoutHeight(int maxLayoutHeight) { @@ -785,10 +802,6 @@ public class NotificationShelf extends ActivatableNotificationView implements updateRelativeOffset(); } - public void setDarkOffsetX(int offsetX) { - mShelfIcons.setDarkOffsetX(offsetX); - } - private class ShelfState extends ExpandableViewState { private float openedAmount; private boolean hasItemsInStableShelf; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java index 492ab44d499b..aea0127764b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar; */ import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; @@ -234,7 +233,7 @@ public class NotificationSnooze extends LinearLayout final int defaultSnooze = mParser.getInt(KEY_DEFAULT_SNOOZE, resources.getInteger(R.integer.config_notification_snooze_time_default)); - final int[] snoozeTimes = parseIntArray(KEY_OPTIONS, + final int[] snoozeTimes = mParser.getIntArray(KEY_OPTIONS, resources.getIntArray(R.array.config_notification_snooze_times)); for (int i = 0; i < snoozeTimes.length && i < sAccessibilityActions.length; i++) { @@ -248,21 +247,6 @@ public class NotificationSnooze extends LinearLayout return options; } - @VisibleForTesting - int[] parseIntArray(final String key, final int[] defaultArray) { - final String value = mParser.getString(key, null); - if (value != null) { - try { - return Arrays.stream(value.split(":")).map(String::trim).mapToInt( - Integer::parseInt).toArray(); - } catch (NumberFormatException e) { - return defaultArray; - } - } else { - return defaultArray; - } - } - private SnoozeOption createOption(int minutes, int accessibilityActionId) { Resources res = getResources(); boolean showInHours = minutes >= 60; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java new file mode 100644 index 000000000000..0044194e886c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java @@ -0,0 +1,58 @@ +/* + * 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.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; + +/** + * Interface for accepting notification updates from {@link NotificationListener}. + */ +public interface NotificationUpdateHandler { + /** + * Add a new notification and update the current notification ranking map. + * + * @param notification Notification to add + * @param ranking RankingMap to update with + */ + void addNotification(StatusBarNotification notification, + NotificationListenerService.RankingMap ranking); + + /** + * Remove a notification and update the current notification ranking map. + * + * @param key Key identifying the notification to remove + * @param ranking RankingMap to update with + */ + void removeNotification(String key, NotificationListenerService.RankingMap ranking); + + /** + * Update a given notification and the current notification ranking map. + * + * @param notification Updated notification + * @param ranking RankingMap to update with + */ + void updateNotification(StatusBarNotification notification, + NotificationListenerService.RankingMap ranking); + + /** + * Update with a new notification ranking map. + * + * @param ranking RankingMap to update with + */ + void updateNotificationRanking(NotificationListenerService.RankingMap ranking); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java new file mode 100644 index 000000000000..cd4c7ae8d57e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -0,0 +1,349 @@ +/* + * 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.content.Context; +import android.content.res.Resources; +import android.service.notification.NotificationListenerService; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.phone.NotificationGroupManager; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Stack; + +/** + * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based + * on their group structure. For example, if a notification becomes bundled with another, + * NotificationViewHierarchyManager will update the view hierarchy to reflect that. It also will + * tell NotificationListContainer which notifications to display, and inform it of changes to those + * notifications that might affect their display. + */ +public class NotificationViewHierarchyManager { + private static final String TAG = "NotificationViewHierarchyManager"; + + private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> + mTmpChildOrderMap = new HashMap<>(); + + // Dependencies: + protected final NotificationLockscreenUserManager mLockscreenUserManager = + Dependency.get(NotificationLockscreenUserManager.class); + protected final NotificationGroupManager mGroupManager = + Dependency.get(NotificationGroupManager.class); + protected final VisualStabilityManager mVisualStabilityManager = + Dependency.get(VisualStabilityManager.class); + + /** + * {@code true} if notifications not part of a group should by default be rendered in their + * expanded state. If {@code false}, then only the first notification will be expanded if + * possible. + */ + private final boolean mAlwaysExpandNonGroupedNotification; + + private NotificationPresenter mPresenter; + private NotificationEntryManager mEntryManager; + private NotificationListContainer mListContainer; + + public NotificationViewHierarchyManager(Context context) { + Resources res = context.getResources(); + mAlwaysExpandNonGroupedNotification = + res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications); + } + + public void setUpWithPresenter(NotificationPresenter presenter, + NotificationEntryManager entryManager, NotificationListContainer listContainer) { + mPresenter = presenter; + mEntryManager = entryManager; + mListContainer = listContainer; + } + + /** + * Updates the visual representation of the notifications. + */ + public void updateNotificationViews() { + ArrayList<NotificationData.Entry> activeNotifications = mEntryManager.getNotificationData() + .getActiveNotifications(); + ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size()); + final int N = activeNotifications.size(); + for (int i = 0; i < N; i++) { + NotificationData.Entry ent = activeNotifications.get(i); + if (ent.row.isDismissed() || ent.row.isRemoved()) { + // we don't want to update removed notifications because they could + // temporarily become children if they were isolated before. + continue; + } + int userId = ent.notification.getUserId(); + + // Display public version of the notification if we need to redact. + // TODO: This area uses a lot of calls into NotificationLockscreenUserManager. + // We can probably move some of this code there. + boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode( + mLockscreenUserManager.getCurrentUserId()); + boolean userPublic = devicePublic + || mLockscreenUserManager.isLockscreenPublicMode(userId); + boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent); + boolean sensitive = userPublic && needsRedaction; + boolean deviceSensitive = devicePublic + && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic( + mLockscreenUserManager.getCurrentUserId()); + ent.row.setSensitive(sensitive, deviceSensitive); + ent.row.setNeedsRedaction(needsRedaction); + if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) { + ExpandableNotificationRow summary = mGroupManager.getGroupSummary( + ent.row.getStatusBarNotification()); + List<ExpandableNotificationRow> orderedChildren = + mTmpChildOrderMap.get(summary); + if (orderedChildren == null) { + orderedChildren = new ArrayList<>(); + mTmpChildOrderMap.put(summary, orderedChildren); + } + orderedChildren.add(ent.row); + } else { + toShow.add(ent.row); + } + + } + + ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); + for (int i=0; i< mListContainer.getContainerChildCount(); i++) { + View child = mListContainer.getContainerChildAt(i); + if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) { + toRemove.add((ExpandableNotificationRow) child); + } + } + + for (ExpandableNotificationRow remove : toRemove) { + if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) { + // we are only transferring this notification to its parent, don't generate an + // animation + mListContainer.setChildTransferInProgress(true); + } + if (remove.isSummaryWithChildren()) { + remove.removeAllChildren(); + } + mListContainer.removeContainerView(remove); + mListContainer.setChildTransferInProgress(false); + } + + removeNotificationChildren(); + + for (int i = 0; i < toShow.size(); i++) { + View v = toShow.get(i); + if (v.getParent() == null) { + mVisualStabilityManager.notifyViewAddition(v); + mListContainer.addContainerView(v); + } + } + + addNotificationChildrenAndSort(); + + // So after all this work notifications still aren't sorted correctly. + // Let's do that now by advancing through toShow and mListContainer in + // lock-step, making sure mListContainer matches what we see in toShow. + int j = 0; + for (int i = 0; i < mListContainer.getContainerChildCount(); i++) { + View child = mListContainer.getContainerChildAt(i); + if (!(child instanceof ExpandableNotificationRow)) { + // We don't care about non-notification views. + continue; + } + + ExpandableNotificationRow targetChild = toShow.get(j); + if (child != targetChild) { + // Oops, wrong notification at this position. Put the right one + // here and advance both lists. + if (mVisualStabilityManager.canReorderNotification(targetChild)) { + mListContainer.changeViewPosition(targetChild, i); + } else { + mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager); + } + } + j++; + + } + + mVisualStabilityManager.onReorderingFinished(); + // clear the map again for the next usage + mTmpChildOrderMap.clear(); + + updateRowStates(); + + mListContainer.onNotificationViewUpdateFinished(); + } + + private void addNotificationChildrenAndSort() { + // Let's now add all notification children which are missing + boolean orderChanged = false; + for (int i = 0; i < mListContainer.getContainerChildCount(); i++) { + View view = mListContainer.getContainerChildAt(i); + if (!(view instanceof ExpandableNotificationRow)) { + // We don't care about non-notification views. + continue; + } + + ExpandableNotificationRow parent = (ExpandableNotificationRow) view; + List<ExpandableNotificationRow> children = parent.getNotificationChildren(); + List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); + + for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size(); + childIndex++) { + ExpandableNotificationRow childView = orderedChildren.get(childIndex); + if (children == null || !children.contains(childView)) { + if (childView.getParent() != null) { + Log.wtf(TAG, "trying to add a notification child that already has " + + "a parent. class:" + childView.getParent().getClass() + + "\n child: " + childView); + // This shouldn't happen. We can recover by removing it though. + ((ViewGroup) childView.getParent()).removeView(childView); + } + mVisualStabilityManager.notifyViewAddition(childView); + parent.addChildNotification(childView, childIndex); + mListContainer.notifyGroupChildAdded(childView); + } + } + + // Finally after removing and adding has been performed we can apply the order. + orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager, + mEntryManager); + } + if (orderChanged) { + mListContainer.generateChildOrderChangedEvent(); + } + } + + private void removeNotificationChildren() { + // First let's remove all children which don't belong in the parents + ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); + for (int i = 0; i < mListContainer.getContainerChildCount(); i++) { + View view = mListContainer.getContainerChildAt(i); + if (!(view instanceof ExpandableNotificationRow)) { + // We don't care about non-notification views. + continue; + } + + ExpandableNotificationRow parent = (ExpandableNotificationRow) view; + List<ExpandableNotificationRow> children = parent.getNotificationChildren(); + List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); + + if (children != null) { + toRemove.clear(); + for (ExpandableNotificationRow childRow : children) { + if ((orderedChildren == null + || !orderedChildren.contains(childRow)) + && !childRow.keepInParent()) { + toRemove.add(childRow); + } + } + for (ExpandableNotificationRow remove : toRemove) { + parent.removeChildNotification(remove); + if (mEntryManager.getNotificationData().get( + remove.getStatusBarNotification().getKey()) == null) { + // We only want to add an animation if the view is completely removed + // otherwise it's just a transfer + mListContainer.notifyGroupChildRemoved(remove, + parent.getChildrenContainer()); + } + } + } + } + } + + /** + * Updates expanded, dimmed and locked states of notification rows. + */ + public void updateRowStates() { + final int N = mListContainer.getContainerChildCount(); + + int visibleNotifications = 0; + boolean isLocked = mPresenter.isPresenterLocked(); + int maxNotifications = -1; + if (isLocked) { + maxNotifications = mPresenter.getMaxNotificationsWhileLocked(true /* recompute */); + } + mListContainer.setMaxDisplayedNotifications(maxNotifications); + Stack<ExpandableNotificationRow> stack = new Stack<>(); + for (int i = N - 1; i >= 0; i--) { + View child = mListContainer.getContainerChildAt(i); + if (!(child instanceof ExpandableNotificationRow)) { + continue; + } + stack.push((ExpandableNotificationRow) child); + } + while(!stack.isEmpty()) { + ExpandableNotificationRow row = stack.pop(); + NotificationData.Entry entry = row.getEntry(); + boolean isChildNotification = + mGroupManager.isChildInGroupWithSummary(entry.notification); + + row.setOnKeyguard(isLocked); + + if (!isLocked) { + // If mAlwaysExpandNonGroupedNotification is false, then only expand the + // very first notification and if it's not a child of grouped notifications. + row.setSystemExpanded(mAlwaysExpandNonGroupedNotification + || (visibleNotifications == 0 && !isChildNotification + && !row.isLowPriority())); + } + + entry.row.setShowAmbient(mPresenter.isDozing()); + int userId = entry.notification.getUserId(); + boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup( + entry.notification) && !entry.row.isRemoved(); + boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry + .notification); + if (suppressedSummary + || (mLockscreenUserManager.isLockscreenPublicMode(userId) + && !mLockscreenUserManager.shouldShowLockscreenNotifications()) + || (isLocked && !showOnKeyguard)) { + entry.row.setVisibility(View.GONE); + } else { + boolean wasGone = entry.row.getVisibility() == View.GONE; + if (wasGone) { + entry.row.setVisibility(View.VISIBLE); + } + if (!isChildNotification && !entry.row.isRemoved()) { + if (wasGone) { + // notify the scroller of a child addition + mListContainer.generateAddAnimation(entry.row, + !showOnKeyguard /* fromMoreCard */); + } + visibleNotifications++; + } + } + if (row.isSummaryWithChildren()) { + List<ExpandableNotificationRow> notificationChildren = + row.getNotificationChildren(); + int size = notificationChildren.size(); + for (int i = size - 1; i >= 0; i--) { + stack.push(notificationChildren.get(i)); + } + } + + row.showBlockingHelper(entry.userSentiment == + NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE); + } + + mPresenter.onUpdateRowStates(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java new file mode 100644 index 000000000000..5090f74d4019 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java @@ -0,0 +1,155 @@ +/* + * 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.content.Context; +import android.net.ConnectivityManager; +import android.graphics.Rect; +import android.os.Bundle; +import android.provider.Settings; +import android.telephony.ServiceState; +import android.telephony.SubscriptionInfo; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.widget.TextView; + +import com.android.internal.telephony.IccCardConstants.State; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.settingslib.WirelessUtils; +import com.android.systemui.DemoMode; +import com.android.systemui.Dependency; +import com.android.systemui.statusbar.policy.DarkIconDispatcher; +import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.statusbar.policy.NetworkController; +import com.android.systemui.statusbar.policy.NetworkController.IconState; +import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; +import com.android.systemui.tuner.TunerService; +import com.android.systemui.tuner.TunerService.Tunable; + +import java.util.List; + +public class OperatorNameView extends TextView implements DemoMode, DarkReceiver, + SignalCallback, Tunable { + + private static final String KEY_SHOW_OPERATOR_NAME = "show_operator_name"; + + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private boolean mDemoMode; + + private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() { + @Override + public void onRefreshCarrierInfo() { + updateText(); + } + }; + + public OperatorNameView(Context context) { + this(context, null); + } + + public OperatorNameView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public OperatorNameView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); + mKeyguardUpdateMonitor.registerCallback(mCallback); + Dependency.get(DarkIconDispatcher.class).addDarkReceiver(this); + Dependency.get(NetworkController.class).addCallback(this); + Dependency.get(TunerService.class).addTunable(this, KEY_SHOW_OPERATOR_NAME); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mKeyguardUpdateMonitor.removeCallback(mCallback); + Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(this); + Dependency.get(NetworkController.class).removeCallback(this); + Dependency.get(TunerService.class).removeTunable(this); + } + + @Override + public void onDarkChanged(Rect area, float darkIntensity, int tint) { + setTextColor(DarkIconDispatcher.getTint(area, this, tint)); + } + + @Override + public void setIsAirplaneMode(IconState icon) { + update(); + } + + @Override + public void onTuningChanged(String key, String newValue) { + update(); + } + + @Override + public void dispatchDemoCommand(String command, Bundle args) { + if (!mDemoMode && command.equals(COMMAND_ENTER)) { + mDemoMode = true; + } else if (mDemoMode && command.equals(COMMAND_EXIT)) { + mDemoMode = false; + update(); + } else if (mDemoMode && command.equals(COMMAND_OPERATOR)) { + setText(args.getString("name")); + } + } + + private void update() { + boolean showOperatorName = Dependency.get(TunerService.class) + .getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0; + setVisibility(showOperatorName ? VISIBLE : GONE); + + boolean hasMobile = ConnectivityManager.from(mContext) + .isNetworkSupported(ConnectivityManager.TYPE_MOBILE); + boolean airplaneMode = WirelessUtils.isAirplaneModeOn(mContext); + if (!hasMobile || airplaneMode) { + setText(null); + setVisibility(GONE); + return; + } + + if (!mDemoMode) { + updateText(); + } + } + + private void updateText() { + CharSequence displayText = null; + List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false); + final int N = subs.size(); + for (int i = 0; i < N; i++) { + int subId = subs.get(i).getSubscriptionId(); + State simState = mKeyguardUpdateMonitor.getSimState(subId); + CharSequence carrierName = subs.get(i).getCarrierName(); + if (!TextUtils.isEmpty(carrierName) && simState == State.READY) { + ServiceState ss = mKeyguardUpdateMonitor.getServiceState(subId); + if (ss != null && ss.getState() == ServiceState.STATE_IN_SERVICE) { + displayText = carrierName; + break; + } + } + } + + setText(displayText); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java index 7f28c4c2e7a4..cfc69a8f67a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java @@ -19,30 +19,82 @@ package com.android.systemui.statusbar; import com.android.internal.util.Preconditions; import com.android.systemui.Dependency; import com.android.systemui.statusbar.phone.StatusBarWindowManager; -import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.RemoteInputView; +import android.app.Notification; +import android.app.RemoteInput; +import android.content.Context; +import android.os.SystemProperties; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.List; /** * Keeps track of the currently active {@link RemoteInputView}s. */ public class RemoteInputController { + private static final boolean ENABLE_REMOTE_INPUT = + SystemProperties.getBoolean("debug.enable_remote_input", true); private final ArrayList<Pair<WeakReference<NotificationData.Entry>, Object>> mOpen = new ArrayList<>(); private final ArrayMap<String, Object> mSpinning = new ArrayMap<>(); private final ArrayList<Callback> mCallbacks = new ArrayList<>(3); - private final HeadsUpManager mHeadsUpManager; + private final Delegate mDelegate; - public RemoteInputController(HeadsUpManager headsUpManager) { - addCallback(Dependency.get(StatusBarWindowManager.class)); - mHeadsUpManager = headsUpManager; + public RemoteInputController(Delegate delegate) { + mDelegate = delegate; + } + + /** + * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this + * via first-class API. + * + * TODO: Remove once enough apps specify remote inputs on their own. + */ + public static void processForRemoteInput(Notification n, Context context) { + if (!ENABLE_REMOTE_INPUT) { + return; + } + + if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") && + (n.actions == null || n.actions.length == 0)) { + Notification.Action viableAction = null; + Notification.WearableExtender we = new Notification.WearableExtender(n); + + List<Notification.Action> actions = we.getActions(); + final int numActions = actions.size(); + + for (int i = 0; i < numActions; i++) { + Notification.Action action = actions.get(i); + if (action == null) { + continue; + } + RemoteInput[] remoteInputs = action.getRemoteInputs(); + if (remoteInputs == null) { + continue; + } + for (RemoteInput ri : remoteInputs) { + if (ri.getAllowFreeFormInput()) { + viableAction = action; + break; + } + } + if (viableAction != null) { + break; + } + } + + if (viableAction != null) { + Notification.Builder rebuilder = Notification.Builder.recoverBuilder(context, n); + rebuilder.setActions(viableAction); + rebuilder.build(); // will rewrite n + } + } } /** @@ -114,7 +166,7 @@ public class RemoteInputController { } private void apply(NotificationData.Entry entry) { - mHeadsUpManager.setRemoteInputActive(entry, isRemoteInputActive(entry)); + mDelegate.setRemoteInputActive(entry, isRemoteInputActive(entry)); boolean remoteInputActive = isRemoteInputActive(); int N = mCallbacks.size(); for (int i = 0; i < N; i++) { @@ -204,9 +256,35 @@ public class RemoteInputController { } } + public void requestDisallowLongPressAndDismiss() { + mDelegate.requestDisallowLongPressAndDismiss(); + } + + public void lockScrollTo(NotificationData.Entry entry) { + mDelegate.lockScrollTo(entry); + } + public interface Callback { default void onRemoteInputActive(boolean active) {} default void onRemoteInputSent(NotificationData.Entry entry) {} } + + public interface Delegate { + /** + * Activate remote input if necessary. + */ + void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive); + + /** + * Request that the view does not dismiss nor perform long press for the current touch. + */ + void requestDisallowLongPressAndDismiss(); + + /** + * Request that the view is made visible by scrolling to it, and keep the scroll locked until + * the user scrolls, or {@param v} loses focus or is detached. + */ + void lockScrollTo(NotificationData.Entry entry); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java index a53e348fd16c..8830352296dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java @@ -41,6 +41,7 @@ import android.view.animation.Interpolator; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.colorextraction.drawable.GradientDrawable; +import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -50,6 +51,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; public class ScrimView extends View implements ConfigurationController.ConfigurationListener { private static final String TAG = "ScrimView"; private final ColorExtractor.GradientColors mColors; + private int mDensity; private boolean mDrawAsSrc; private float mViewAlpha = 1.0f; private ValueAnimator mAlphaAnimator; @@ -72,6 +74,7 @@ public class ScrimView extends View implements ConfigurationController.Configura } }; private Runnable mChangeRunnable; + private int mCornerRadius; public ScrimView(Context context) { this(context, null); @@ -93,6 +96,24 @@ public class ScrimView extends View implements ConfigurationController.Configura mColors = new ColorExtractor.GradientColors(); updateScreenSize(); updateColorWithTint(false); + initView(); + final Configuration currentConfig = mContext.getResources().getConfiguration(); + mDensity = currentConfig.densityDpi; + } + + private void initView() { + mCornerRadius = getResources().getDimensionPixelSize( + Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + int densityDpi = newConfig.densityDpi; + if (mDensity != densityDpi) { + mDensity = densityDpi; + initView(); + } } @Override @@ -145,6 +166,28 @@ public class ScrimView extends View implements ConfigurationController.Configura mDrawable.draw(canvas); canvas.restore(); } + // We also need to draw the rounded corners of the background + canvas.save(); + canvas.clipRect(mExcludedRect.left, mExcludedRect.top, + mExcludedRect.left + mCornerRadius, mExcludedRect.top + mCornerRadius); + mDrawable.draw(canvas); + canvas.restore(); + canvas.save(); + canvas.clipRect(mExcludedRect.right - mCornerRadius, mExcludedRect.top, + mExcludedRect.right, mExcludedRect.top + mCornerRadius); + mDrawable.draw(canvas); + canvas.restore(); + canvas.save(); + canvas.clipRect(mExcludedRect.left, mExcludedRect.bottom - mCornerRadius, + mExcludedRect.left + mCornerRadius, mExcludedRect.bottom); + mDrawable.draw(canvas); + canvas.restore(); + canvas.save(); + canvas.clipRect(mExcludedRect.right - mCornerRadius, + mExcludedRect.bottom - mCornerRadius, + mExcludedRect.right, mExcludedRect.bottom); + mDrawable.draw(canvas); + canvas.restore(); } } } @@ -252,6 +295,13 @@ public class ScrimView extends View implements ConfigurationController.Configura return false; } + /** + * It might look counterintuitive to have another method to set the alpha instead of + * only using {@link #setAlpha(float)}. In this case we're in a hardware layer + * optimizing blend modes, so it makes sense. + * + * @param alpha Gradient alpha from 0 to 1. + */ public void setViewAlpha(float alpha) { if (alpha != mViewAlpha) { mViewAlpha = alpha; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java index 274244ef2679..cb6e5a6e0bae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar; +import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS; +import static android.app.StatusBarManager.DISABLE_NONE; + import android.annotation.DrawableRes; import android.content.Context; import android.content.res.ColorStateList; @@ -35,9 +38,9 @@ import android.view.accessibility.AccessibilityEvent; import android.widget.ImageView; import android.widget.LinearLayout; +import com.android.settingslib.graph.SignalDrawable; import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.statusbar.phone.SignalDrawable; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.policy.DarkIconDispatcher; import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver; @@ -48,6 +51,7 @@ import com.android.systemui.statusbar.policy.NetworkControllerImpl; import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; +import com.android.systemui.util.Utils.DisableStateTracker; import java.util.ArrayList; import java.util.List; @@ -55,8 +59,7 @@ import java.util.Objects; // Intimately tied to the design of res/layout/signal_cluster_view.xml public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback, - SecurityController.SecurityControllerCallback, Tunable, - DarkReceiver { + SecurityController.SecurityControllerCallback, Tunable, DarkReceiver { static final String TAG = "SignalClusterView"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -148,6 +151,8 @@ public class SignalClusterView extends LinearLayout implements NetworkController mIconScaleFactor = typedValue.getFloat(); mNetworkController = Dependency.get(NetworkController.class); mSecurityController = Dependency.get(SecurityController.class); + addOnAttachStateChangeListener( + new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS)); updateActivityEnabled(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java index 535300573c6e..09b11c295701 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java @@ -35,7 +35,8 @@ import java.util.Stack; /** * A view that can be transformed to and from. */ -public class ViewTransformationHelper implements TransformableView { +public class ViewTransformationHelper implements TransformableView, + TransformState.TransformInfo { private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view; @@ -59,7 +60,7 @@ public class ViewTransformationHelper implements TransformableView { public TransformState getCurrentState(int fadingView) { View view = mTransformedViews.get(fadingView); if (view != null && view.getVisibility() != View.GONE) { - return TransformState.createFrom(view); + return TransformState.createFrom(view, this); } return null; } @@ -88,6 +89,7 @@ public class ViewTransformationHelper implements TransformableView { endRunnable.run(); } setVisible(false); + mViewTransformationAnimation = null; } else { abortTransformations(); } @@ -245,7 +247,7 @@ public class ViewTransformationHelper implements TransformableView { } public void resetTransformedView(View view) { - TransformState state = TransformState.createFrom(view); + TransformState state = TransformState.createFrom(view, this); state.setVisible(true /* visible */, true /* force */); state.recycle(); } @@ -257,6 +259,11 @@ public class ViewTransformationHelper implements TransformableView { return new ArraySet<>(mTransformedViews.values()); } + @Override + public boolean isAnimating() { + return mViewTransformationAnimation != null && mViewTransformationAnimation.isRunning(); + } + public static abstract class CustomTransformation { /** * Transform a state to the given view diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java index 7e08d5605f40..64c52ed6d29f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java @@ -15,7 +15,13 @@ */ package com.android.systemui.statusbar.car; -import android.app.ActivityManager.StackId; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; + +import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -101,7 +107,7 @@ class CarNavigationBarController { } } - public void taskChanged(String packageName, int stackId) { + public void taskChanged(String packageName, ActivityManager.RunningTaskInfo taskInfo) { // If the package name belongs to a filter, then highlight appropriate button in // the navigation bar. if (mFacetPackageMap.containsKey(packageName)) { @@ -115,9 +121,11 @@ class CarNavigationBarController { } // Set up the persistent docked task if needed. - if (mPersistentTaskIntent != null && !mStatusBar.hasDockedTask() - && stackId != StackId.HOME_STACK_ID) { - mStatusBar.startActivityOnStack(mPersistentTaskIntent, StackId.DOCKED_STACK_ID); + boolean isHomeTask = + taskInfo.configuration.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME; + if (mPersistentTaskIntent != null && !mStatusBar.hasDockedTask() && !isHomeTask) { + mStatusBar.startActivityOnStack(mPersistentTaskIntent, + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED); } } @@ -361,7 +369,7 @@ class CarNavigationBarController { private void onFacetClicked(Intent intent, int index) { String packageName = intent.getPackage(); - if (packageName == null) { + if (packageName == null && !intent.getCategories().contains(Intent.CATEGORY_HOME)) { return; } @@ -375,13 +383,15 @@ class CarNavigationBarController { // rather than the "preferred/last run" app. intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, index == mCurrentFacetIndex); - int stackId = StackId.FULLSCREEN_WORKSPACE_STACK_ID; + int windowingMode = WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; + int activityType = ACTIVITY_TYPE_UNDEFINED; if (intent.getCategories().contains(Intent.CATEGORY_HOME)) { - stackId = StackId.HOME_STACK_ID; + windowingMode = WINDOWING_MODE_UNDEFINED; + activityType = ACTIVITY_TYPE_HOME; } setCurrentFacet(index); - mStatusBar.startActivityOnStack(intent, stackId); + mStatusBar.startActivityOnStack(intent, windowingMode, activityType); } /** @@ -391,6 +401,7 @@ class CarNavigationBarController { */ private void onFacetLongClicked(Intent intent, int index) { setCurrentFacet(index); - mStatusBar.startActivityOnStack(intent, StackId.FULLSCREEN_WORKSPACE_STACK_ID); + mStatusBar.startActivityOnStack(intent, + WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java index 6cbbd6cd1f18..e5a311d099d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java @@ -17,11 +17,15 @@ package com.android.systemui.statusbar.car; import android.content.Context; +import android.graphics.Canvas; import android.util.AttributeSet; +import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; import com.android.systemui.R; +import com.android.systemui.plugins.statusbar.phone.NavGesture; +import com.android.systemui.statusbar.phone.NavigationBarGestureHelper; import com.android.systemui.statusbar.phone.NavigationBarView; /** @@ -72,4 +76,68 @@ class CarNavigationBarView extends NavigationBarView { // Calling setNavigationIconHints in the base class will result in a NPE as the car // navigation bar does not have a back button. } + + @Override + public void onPluginConnected(NavGesture plugin, Context context) { + // set to null version of the plugin ignoring incoming arg. + super.onPluginConnected(new NullNavGesture(), context); + } + + @Override + public void onPluginDisconnected(NavGesture plugin) { + // reinstall the null nav gesture plugin + super.onPluginConnected(new NullNavGesture(), getContext()); + } + + /** + * Null object pattern to work around expectations of the base class. + * This is a temporary solution to have the car system ui working. + * Already underway is a refactor of they car sys ui as to not use this class + * hierarchy. + */ + private static class NullNavGesture implements NavGesture { + @Override + public GestureHelper getGestureHelper() { + return new GestureHelper() { + @Override + public boolean onTouchEvent(MotionEvent event) { + return false; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + return false; + } + + @Override + public void setBarState(boolean vertical, boolean isRtl) { + } + + @Override + public void onDraw(Canvas canvas) { + } + + @Override + public void onDarkIntensityChange(float intensity) { + } + + @Override + public void onLayout(boolean changed, int left, int top, int right, int bottom) { + } + }; + } + + @Override + public int getVersion() { + return 0; + } + + @Override + public void onCreate(Context sysuiContext, Context pluginContext) { + } + + @Override + public void onDestroy() { + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 680f693a83f8..3dfb9130af2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -36,14 +36,19 @@ import android.view.ViewStub; import android.view.WindowManager; import android.widget.LinearLayout; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.BatteryMeterView; import com.android.systemui.Dependency; +import com.android.systemui.Prefs; import com.android.systemui.R; -import com.android.systemui.SwipeHelper; +import com.android.systemui.classifier.FalsingLog; +import com.android.systemui.classifier.FalsingManager; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.recents.Recents; +import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; @@ -51,10 +56,6 @@ import com.android.systemui.statusbar.phone.NavigationBarView; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.classifier.FalsingLog; -import com.android.systemui.classifier.FalsingManager; -import com.android.systemui.Prefs; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -84,7 +85,7 @@ public class CarStatusBar extends StatusBar implements public void start() { super.start(); mTaskStackListener = new TaskStackListenerImpl(); - SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener); + ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); registerPackageChangeReceivers(); mStackScroller.setScrollingEnabled(true); @@ -246,18 +247,6 @@ public class CarStatusBar extends StatusBar implements return null; } - /** - * Returns the {@link com.android.systemui.SwipeHelper.LongPressListener} that will be - * triggered when a notification card is long-pressed. - */ - @Override - protected SwipeHelper.LongPressListener getNotificationLongClicker() { - // For the automative use case, we do not want to the user to be able to interact with - // a notification other than a regular click. As a result, just return null for the - // long click listener. - return null; - } - @Override public void showBatteryView() { if (Log.isLoggable(TAG, Log.DEBUG)) { @@ -304,17 +293,17 @@ public class CarStatusBar extends StatusBar implements } /** - * An implementation of TaskStackListener, that listens for changes in the system task + * An implementation of SysUiTaskStackChangeListener, that listens for changes in the system task * stack and notifies the navigation bar. */ - private class TaskStackListenerImpl extends TaskStackListener { + private class TaskStackListenerImpl extends SysUiTaskStackChangeListener { @Override public void onTaskStackChanged() { - SystemServicesProxy ssp = Recents.getSystemServices(); - ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask(); + ActivityManager.RunningTaskInfo runningTaskInfo = + ActivityManagerWrapper.getInstance().getRunningTask(); if (runningTaskInfo != null && runningTaskInfo.baseActivity != null) { mController.taskChanged(runningTaskInfo.baseActivity.getPackageName(), - runningTaskInfo.stackId); + runningTaskInfo); } } } @@ -333,8 +322,8 @@ public class CarStatusBar extends StatusBar implements } @Override - public void userSwitched(int newUserId) { - super.userSwitched(newUserId); + public void onUserSwitched(int newUserId) { + super.onUserSwitched(newUserId); if (mFullscreenUserSwitcher != null) { mFullscreenUserSwitcher.onUserSwitched(newUserId); } @@ -378,30 +367,19 @@ public class CarStatusBar extends StatusBar implements return result; } - public int startActivityOnStack(Intent intent, int stackId) { - ActivityOptions options = ActivityOptions.makeBasic(); - options.setLaunchStackId(stackId); + public int startActivityOnStack(Intent intent, int windowingMode, int activityType) { + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchWindowingMode(windowingMode); + options.setLaunchActivityType(activityType); return startActivityWithOptions(intent, options.toBundle()); } @Override - protected boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) { - // Because space is usually constrained in the auto use-case, there should not be a - // pinned notification when the shade has been expanded. Ensure this by not pinning any - // notification if the shade is already opened. - if (mPanelExpanded) { - return false; - } - - return super.shouldPeek(entry, sbn); - } - - @Override public void animateExpandNotificationsPanel() { // Because space is usually constrained in the auto use-case, there should not be a // pinned notification when the shade has been expanded. Ensure this by removing all heads- // up notifications. - mHeadsUpManager.removeAllHeadsUpEntries(); + mHeadsUpManager.releaseAllImmediately(); super.animateExpandNotificationsPanel(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java index 677fa81a12cd..0304086dbfda 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java @@ -16,10 +16,10 @@ import android.util.Log; import android.util.TypedValue; import android.view.View; import android.widget.ImageView; +import com.android.settingslib.graph.SignalDrawable; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.statusbar.ScalingDrawableWrapper; -import com.android.systemui.statusbar.phone.SignalDrawable; import com.android.systemui.statusbar.policy.BluetoothController; import static com.android.systemui.statusbar.phone.StatusBar.DEBUG; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java index 172c62a99db2..3ec89138d1c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java @@ -53,7 +53,7 @@ public class FullscreenUserSwitcher { mParent = containerStub.inflate(); mContainer = mParent.findViewById(R.id.container); mUserGridView = mContainer.findViewById(R.id.user_grid); - mUserGridView.init(statusBar, mUserSwitcherController, true /* showInitially */); + mUserGridView.init(statusBar, mUserSwitcherController, true /* overrideAlpha */); mUserGridView.setUserSelectionListener(record -> { if (!record.isCurrent) { toggleSwitchInProgress(true); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java index e551801ca434..1bd820db3f7c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java @@ -16,9 +16,6 @@ package com.android.systemui.statusbar.car; -import android.animation.Animator; -import android.animation.Animator.AnimatorListener; -import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; @@ -29,62 +26,110 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; -import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.TextView; +import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.qs.car.CarQSFragment; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserSwitcherController; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Vector; + /** * Displays a ViewPager with icons for the users in the system to allow switching between users. * One of the uses of this is for the lock screen in auto. */ -public class UserGridView extends ViewPager { - private static final int EXPAND_ANIMATION_TIME_MS = 200; - private static final int HIDE_ANIMATION_TIME_MS = 133; - +public class UserGridView extends ViewPager implements + UserInfoController.OnUserInfoChangedListener { private StatusBar mStatusBar; private UserSwitcherController mUserSwitcherController; private Adapter mAdapter; private UserSelectionListener mUserSelectionListener; - private ValueAnimator mHeightAnimator; - private int mTargetHeight; - private int mHeightChildren; - private boolean mShowing; + private UserInfoController mUserInfoController; + private Vector mUserContainers; + private int mContainerWidth; + private boolean mOverrideAlpha; + private CarQSFragment.UserSwitchCallback mUserSwitchCallback; public UserGridView(Context context, AttributeSet attrs) { super(context, attrs); } public void init(StatusBar statusBar, UserSwitcherController userSwitcherController, - boolean showInitially) { + boolean overrideAlpha) { mStatusBar = statusBar; mUserSwitcherController = userSwitcherController; mAdapter = new Adapter(mUserSwitcherController); - addOnLayoutChangeListener(mAdapter); - setAdapter(mAdapter); - mShowing = showInitially; + mUserInfoController = Dependency.get(UserInfoController.class); + mOverrideAlpha = overrideAlpha; + // Whenever the container width changes, the containers must be refreshed. Instead of + // doing an initial refreshContainers() to populate the containers, this listener will + // refresh them on layout change because that affects how the users are split into + // containers. Furthermore, at this point, the container width is unknown, so + // refreshContainers() cannot populate any containers. + addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + int newWidth = Math.max(left - right, right - left); + if (mContainerWidth != newWidth) { + mContainerWidth = newWidth; + refreshContainers(); + } + }); } - public boolean isShowing() { - return mShowing; + private void refreshContainers() { + mUserContainers = new Vector(); + + Context context = getContext(); + LayoutInflater inflater = LayoutInflater.from(context); + + for (int i = 0; i < mAdapter.getCount(); i++) { + ViewGroup pods = (ViewGroup) inflater.inflate( + R.layout.car_fullscreen_user_pod_container, null); + + int iconsPerPage = mAdapter.getIconsPerPage(); + int limit = Math.min(mUserSwitcherController.getUsers().size(), (i + 1) * iconsPerPage); + for (int j = i * iconsPerPage; j < limit; j++) { + View v = mAdapter.makeUserPod(inflater, context, j, pods); + if (mOverrideAlpha) { + v.setAlpha(1f); + } + pods.addView(v); + // This is hacky, but the dividers on the pod container LinearLayout don't seem + // to work for whatever reason. Instead, set a right margin on the pod if it's not + // the right-most pod and there is more than one pod in the container. + if (i < limit - 1 && limit > 1) { + ViewGroup.MarginLayoutParams params = + (ViewGroup.MarginLayoutParams) v.getLayoutParams(); + params.setMargins(0, 0, getResources().getDimensionPixelSize( + R.dimen.car_fullscreen_user_pod_margin_between), 0); + v.setLayoutParams(params); + } + } + mUserContainers.add(pods); + } + + mAdapter = new Adapter(mUserSwitcherController); + setAdapter(mAdapter); } - public void show() { - mShowing = true; - animateHeightChange(getMeasuredHeight(), mHeightChildren); + @Override + public void onUserInfoChanged(String name, Drawable picture, String userAccount) { + refreshContainers(); } - public void hide() { - mShowing = false; - animateHeightChange(getMeasuredHeight(), 0); + public void setUserSwitchCallback(CarQSFragment.UserSwitchCallback callback) { + mUserSwitchCallback = callback; } public void onUserSwitched(int newUserId) { @@ -96,6 +141,14 @@ public class UserGridView extends ViewPager { mUserSelectionListener = userSelectionListener; } + public void setListening(boolean listening) { + if (listening) { + mUserInfoController.addCallback(this); + } else { + mUserInfoController.removeCallback(this); + } + } + void showOfflineAuthUi() { // TODO: Show keyguard UI in-place. mStatusBar.executeRunnableDismissingKeyguard(null, null, true, true, true); @@ -115,13 +168,6 @@ public class UserGridView extends ViewPager { height = Math.max(child.getMeasuredHeight(), height); } - mHeightChildren = height; - - // Override the height if it's not showing. - if (!mShowing) { - height = 0; - } - // Respect the AT_MOST request from parent. if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) { height = Math.min(MeasureSpec.getSize(heightMeasureSpec), height); @@ -132,72 +178,19 @@ public class UserGridView extends ViewPager { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } - private void animateHeightChange(int oldHeight, int newHeight) { - // If there is no change in height or an animation is already in progress towards the - // desired height, then there's no need to make any changes. - if (oldHeight == newHeight || newHeight == mTargetHeight) { - return; - } - - // Animation in progress is not going towards the new target, so cancel it. - if (mHeightAnimator != null){ - mHeightAnimator.cancel(); - } - - mTargetHeight = newHeight; - mHeightAnimator = ValueAnimator.ofInt(oldHeight, mTargetHeight); - mHeightAnimator.addUpdateListener(valueAnimator -> { - ViewGroup.LayoutParams layoutParams = getLayoutParams(); - layoutParams.height = (Integer) valueAnimator.getAnimatedValue(); - requestLayout(); - }); - mHeightAnimator.addListener(new AnimatorListener() { - @Override - public void onAnimationStart(Animator animator) {} - - @Override - public void onAnimationEnd(Animator animator) { - // ValueAnimator does not guarantee that the update listener will get an update - // to the final value, so here, the final value is set. Though the final calculated - // height (mTargetHeight) could be set, WRAP_CONTENT is more appropriate. - ViewGroup.LayoutParams layoutParams = getLayoutParams(); - layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; - requestLayout(); - mHeightAnimator = null; - } - - @Override - public void onAnimationCancel(Animator animator) {} - - @Override - public void onAnimationRepeat(Animator animator) {} - }); - - mHeightAnimator.setInterpolator(new FastOutSlowInInterpolator()); - if (oldHeight < newHeight) { - // Expanding - mHeightAnimator.setDuration(EXPAND_ANIMATION_TIME_MS); - } else { - // Hiding - mHeightAnimator.setDuration(HIDE_ANIMATION_TIME_MS); - } - mHeightAnimator.start(); - } - /** * This is a ViewPager.PagerAdapter which deletegates the work to a * UserSwitcherController.BaseUserAdapter. Java doesn't support multiple inheritance so we have * to use composition instead to achieve the same goal since both the base classes are abstract * classes and not interfaces. */ - private final class Adapter extends PagerAdapter implements View.OnLayoutChangeListener { + private final class Adapter extends PagerAdapter { private final int mPodWidth; private final int mPodMarginBetween; private final int mPodImageAvatarWidth; private final int mPodImageAvatarHeight; private final WrappedBaseUserAdapter mUserAdapter; - private int mContainerWidth; public Adapter(UserSwitcherController controller) { super(); @@ -229,30 +222,20 @@ public class UserGridView extends ViewPager { } @Override - public Object instantiateItem(ViewGroup container, int position) { - Context context = getContext(); - LayoutInflater inflater = LayoutInflater.from(context); - - ViewGroup pods = (ViewGroup) inflater.inflate( - R.layout.car_fullscreen_user_pod_container, null); + public void finishUpdate(ViewGroup container) { + if (mUserSwitchCallback != null) { + mUserSwitchCallback.resetShowing(); + } + } - int iconsPerPage = getIconsPerPage(); - int limit = Math.min(mUserAdapter.getCount(), (position + 1) * iconsPerPage); - for (int i = position * iconsPerPage; i < limit; i++) { - View v = makeUserPod(inflater, context, i, pods); - pods.addView(v); - // This is hacky, but the dividers on the pod container LinearLayout don't seem - // to work for whatever reason. Instead, set a right margin on the pod if it's not - // the right-most pod and there is more than one pod in the container. - if (i < limit - 1 && limit > 1) { - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - params.setMargins(0, 0, mPodMarginBetween, 0); - v.setLayoutParams(params); - } + @Override + public Object instantiateItem(ViewGroup container, int position) { + if (position < mUserContainers.size()) { + container.addView((View) mUserContainers.get(position)); + return mUserContainers.get(position); + } else { + return null; } - container.addView(pods); - return pods; } /** @@ -353,17 +336,10 @@ public class UserGridView extends ViewPager { public boolean isViewFromObject(View view, Object object) { return view == object; } - - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - mContainerWidth = Math.max(left - right, right - left); - notifyDataSetChanged(); - } } private final class WrappedBaseUserAdapter extends UserSwitcherController.BaseUserAdapter { - private Adapter mContainer; + private final Adapter mContainer; public WrappedBaseUserAdapter(UserSwitcherController controller, Adapter container) { super(controller); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java new file mode 100644 index 000000000000..11d20b221051 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.notification; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.os.RemoteException; +import android.util.MathUtils; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; +import android.view.RemoteAnimationAdapter; +import android.view.RemoteAnimationTarget; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.ViewRootImpl; + +import com.android.systemui.Interpolators; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationListContainer; +import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; +import com.android.systemui.statusbar.phone.NotificationPanelView; +import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.phone.StatusBarWindowView; + +import java.util.function.Consumer; + +/** + * A class that allows activities to be launched in a seamless way where the notification + * transforms nicely into the starting window. + */ +public class ActivityLaunchAnimator { + + private static final int ANIMATION_DURATION = 400; + public static final long ANIMATION_DURATION_FADE_CONTENT = 67; + public static final long ANIMATION_DURATION_FADE_APP = 200; + public static final long ANIMATION_DELAY_ICON_FADE_IN = ANIMATION_DURATION - + CollapsedStatusBarFragment.FADE_IN_DURATION - CollapsedStatusBarFragment.FADE_IN_DELAY + - 16; + private static final long LAUNCH_TIMEOUT = 500; + private final NotificationPanelView mNotificationPanel; + private final NotificationListContainer mNotificationContainer; + private final StatusBarWindowView mStatusBarWindow; + private StatusBar mStatusBar; + private final Runnable mTimeoutRunnable = () -> { + setAnimationPending(false); + mStatusBar.collapsePanel(true /* animate */); + }; + private boolean mAnimationPending; + + public ActivityLaunchAnimator(StatusBarWindowView statusBarWindow, + StatusBar statusBar, + NotificationPanelView notificationPanel, + NotificationListContainer container) { + mNotificationPanel = notificationPanel; + mNotificationContainer = container; + mStatusBarWindow = statusBarWindow; + mStatusBar = statusBar; + } + + public ActivityOptions getLaunchAnimation( + ExpandableNotificationRow sourceNofitication) { + AnimationRunner animationRunner = new AnimationRunner(sourceNofitication); + return ActivityOptions.makeRemoteAnimation( + new RemoteAnimationAdapter(animationRunner, 1000 /* Duration */, 0 /* delay */)); + } + + public boolean isAnimationPending() { + return mAnimationPending; + } + + public void setLaunchResult(int launchResult) { + setAnimationPending((launchResult == ActivityManager.START_TASK_TO_FRONT + || launchResult == ActivityManager.START_SUCCESS) + && mStatusBar.getBarState() == StatusBarState.SHADE); + } + + private void setAnimationPending(boolean pending) { + mAnimationPending = pending; + mStatusBarWindow.setExpandAnimationPending(pending); + if (pending) { + mStatusBarWindow.postDelayed(mTimeoutRunnable, LAUNCH_TIMEOUT); + } else { + mStatusBarWindow.removeCallbacks(mTimeoutRunnable); + } + } + + class AnimationRunner extends IRemoteAnimationRunner.Stub { + + private final ExpandableNotificationRow mSourceNotification; + private final ExpandAnimationParameters mParams; + private final Rect mWindowCrop = new Rect(); + private boolean mLeashShown; + private boolean mInstantCollapsePanel = true; + + public AnimationRunner(ExpandableNotificationRow sourceNofitication) { + mSourceNotification = sourceNofitication; + mParams = new ExpandAnimationParameters(); + } + + @Override + public void onAnimationStart(RemoteAnimationTarget[] remoteAnimationTargets, + IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback) + throws RemoteException { + mSourceNotification.post(() -> { + for (RemoteAnimationTarget app : remoteAnimationTargets) { + if (app.mode == RemoteAnimationTarget.MODE_OPENING) { + setExpandAnimationRunning(true); + mInstantCollapsePanel = app.position.y == 0 + && app.sourceContainerBounds.height() + >= mNotificationPanel.getHeight(); + if (!mInstantCollapsePanel) { + mNotificationPanel.collapseWithDuration(ANIMATION_DURATION); + } + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + mParams.startPosition = mSourceNotification.getLocationOnScreen(); + mParams.startTranslationZ = mSourceNotification.getTranslationZ(); + int targetWidth = app.sourceContainerBounds.width(); + int notificationHeight = mSourceNotification.getActualHeight(); + int notificationWidth = mSourceNotification.getWidth(); + anim.setDuration(ANIMATION_DURATION); + anim.setInterpolator(Interpolators.LINEAR); + anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mParams.linearProgress = animation.getAnimatedFraction(); + float progress + = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( + mParams.linearProgress); + int newWidth = (int) MathUtils.lerp(notificationWidth, + targetWidth, progress); + mParams.left = (int) ((targetWidth - newWidth) / 2.0f); + mParams.right = mParams.left + newWidth; + mParams.top = (int) MathUtils.lerp(mParams.startPosition[1], + app.position.y, progress); + mParams.bottom = (int) MathUtils.lerp(mParams.startPosition[1] + + notificationHeight, + app.position.y + app.sourceContainerBounds.bottom, + progress); + applyParamsToWindow(app); + applyParamsToNotification(mParams); + applyParamsToNotificationList(mParams); + } + }); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + setExpandAnimationRunning(false); + if (mInstantCollapsePanel) { + mStatusBar.collapsePanel(false /* animate */); + } + try { + iRemoteAnimationFinishedCallback.onAnimationFinished(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + }); + anim.start(); + break; + } + } + setAnimationPending(false); + }); + } + + private void setExpandAnimationRunning(boolean running) { + mNotificationPanel.setLaunchingNotification(running); + mSourceNotification.setExpandAnimationRunning(running); + mStatusBarWindow.setExpandAnimationRunning(running); + mNotificationContainer.setExpandingNotification(running ? mSourceNotification : null); + if (!running) { + applyParamsToNotification(null); + applyParamsToNotificationList(null); + } + + } + + private void applyParamsToNotificationList(ExpandAnimationParameters params) { + mNotificationContainer.applyExpandAnimationParams(params); + mNotificationPanel.applyExpandAnimationParams(params); + } + + private void applyParamsToNotification(ExpandAnimationParameters params) { + mSourceNotification.applyExpandAnimationParams(params); + } + + private void applyParamsToWindow(RemoteAnimationTarget app) { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + if (!mLeashShown) { + t.show(app.leash); + mLeashShown = true; + } + Matrix m = new Matrix(); + m.postTranslate(0, (float) (mParams.top - app.position.y)); + t.setMatrix(app.leash, m, new float[9]); + mWindowCrop.set(mParams.left, 0, mParams.right, mParams.getHeight()); + t.setWindowCrop(app.leash, mWindowCrop); + ViewRootImpl viewRootImpl = mSourceNotification.getViewRootImpl(); + if (viewRootImpl != null) { + Surface systemUiSurface = viewRootImpl.mSurface; + t.deferTransactionUntilSurface(app.leash, systemUiSurface, + systemUiSurface.getNextFrameNumber()); + } + t.apply(); + } + + @Override + public void onAnimationCancelled() throws RemoteException { + mSourceNotification.post(() -> { + setAnimationPending(false); + mStatusBar.onLaunchAnimationCancelled(); + }); + } + }; + + public static class ExpandAnimationParameters { + float linearProgress; + int[] startPosition; + float startTranslationZ; + int left; + int top; + int right; + int bottom; + + public ExpandAnimationParameters() { + } + + public int getTop() { + return top; + } + + public int getWidth() { + return right - left; + } + + public int getHeight() { + return bottom - top; + } + + public int getTopChange() { + return Math.min(top - startPosition[1], 0); + } + + + public float getProgress(long delay, long duration) { + return MathUtils.constrain((linearProgress * ANIMATION_DURATION - delay) + / duration, 0.0f, 1.0f); + } + + public float getStartTranslationZ() { + return startTranslationZ; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java new file mode 100644 index 000000000000..d7b211f90be5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java @@ -0,0 +1,77 @@ +/* + * 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.notification; + +import android.util.FloatProperty; +import android.util.Property; +import android.view.View; + +import com.android.systemui.statusbar.stack.AnimationProperties; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * An animatable property of a view. Used with {@link PropertyAnimator} + */ +public interface AnimatableProperty { + int getAnimationStartTag(); + + int getAnimationEndTag(); + + int getAnimatorTag(); + + Property getProperty(); + + static <T extends View> AnimatableProperty from(String name, BiConsumer<T, Float> setter, + Function<T, Float> getter, int animatorTag, int startValueTag, int endValueTag) { + Property<T, Float> property = new FloatProperty<T>(name) { + + @Override + public Float get(T object) { + return getter.apply(object); + } + + @Override + public void setValue(T object, float value) { + setter.accept(object, value); + } + }; + return new AnimatableProperty() { + @Override + public int getAnimationStartTag() { + return startValueTag; + } + + @Override + public int getAnimationEndTag() { + return endValueTag; + } + + @Override + public int getAnimatorTag() { + return animatorTag; + } + + @Override + public Property getProperty() { + return property; + } + }; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java index 92f26d65f25e..d3a325d3b91a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java @@ -21,6 +21,8 @@ import android.util.Pools; import android.view.View; import android.widget.ImageView; +import com.android.internal.widget.MessagingImageMessage; +import com.android.internal.widget.MessagingMessage; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; @@ -39,8 +41,8 @@ public class ImageTransformState extends TransformState { private Icon mIcon; @Override - public void initFrom(View view) { - super.initFrom(view); + public void initFrom(View view, TransformInfo transformInfo) { + super.initFrom(view, transformInfo); if (view instanceof ImageView) { mIcon = (Icon) view.getTag(ICON_TAG); } @@ -117,13 +119,15 @@ public class ImageTransformState extends TransformState { @Override protected boolean transformScale(TransformState otherState) { - return true; + return sameAs(otherState); } @Override public void recycle() { super.recycle(); - sInstancePool.release(this); + if (getClass() == ImageTransformState.class) { + sInstancePool.release(this); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java new file mode 100644 index 000000000000..b97995dd5f93 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java @@ -0,0 +1,134 @@ +/* + * 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.notification; + +import android.util.Pools; +import android.view.View; + +import com.android.internal.widget.MessagingImageMessage; +import com.android.systemui.Interpolators; +import com.android.systemui.R; +import com.android.systemui.statusbar.ViewTransformationHelper; + +/** + * A transform state of a image view. +*/ +public class MessagingImageTransformState extends ImageTransformState { + private static Pools.SimplePool<MessagingImageTransformState> sInstancePool + = new Pools.SimplePool<>(40); + private static final int START_ACTUAL_WIDTH = R.id.transformation_start_actual_width; + private static final int START_ACTUAL_HEIGHT = R.id.transformation_start_actual_height; + private MessagingImageMessage mImageMessage; + + @Override + public void initFrom(View view, TransformInfo transformInfo) { + super.initFrom(view, transformInfo); + mImageMessage = (MessagingImageMessage) view; + } + + @Override + protected boolean sameAs(TransformState otherState) { + if (super.sameAs(otherState)) { + return true; + } + if (otherState instanceof MessagingImageTransformState) { + MessagingImageTransformState otherMessage = (MessagingImageTransformState) otherState; + return mImageMessage.sameAs(otherMessage.mImageMessage); + } + return false; + } + + public static MessagingImageTransformState obtain() { + MessagingImageTransformState instance = sInstancePool.acquire(); + if (instance != null) { + return instance; + } + return new MessagingImageTransformState(); + } + + @Override + protected boolean transformScale(TransformState otherState) { + return false; + } + + @Override + protected void transformViewFrom(TransformState otherState, int transformationFlags, + ViewTransformationHelper.CustomTransformation customTransformation, + float transformationAmount) { + super.transformViewFrom(otherState, transformationFlags, customTransformation, + transformationAmount); + float interpolatedValue = mDefaultInterpolator.getInterpolation( + transformationAmount); + if (otherState instanceof MessagingImageTransformState && sameAs(otherState)) { + MessagingImageMessage otherMessage + = ((MessagingImageTransformState) otherState).mImageMessage; + if (transformationAmount == 0.0f) { + setStartActualWidth(otherMessage.getActualWidth()); + setStartActualHeight(otherMessage.getActualHeight()); + } + float startActualWidth = getStartActualWidth(); + mImageMessage.setActualWidth( + (int) NotificationUtils.interpolate(startActualWidth, + mImageMessage.getStaticWidth(), + interpolatedValue)); + float startActualHeight = getStartActualHeight(); + mImageMessage.setActualHeight( + (int) NotificationUtils.interpolate(startActualHeight, + mImageMessage.getHeight(), + interpolatedValue)); + } + } + + public int getStartActualWidth() { + Object tag = mTransformedView.getTag(START_ACTUAL_WIDTH); + return tag == null ? -1 : (int) tag; + } + + public void setStartActualWidth(int actualWidth) { + mTransformedView.setTag(START_ACTUAL_WIDTH, actualWidth); + } + + public int getStartActualHeight() { + Object tag = mTransformedView.getTag(START_ACTUAL_HEIGHT); + return tag == null ? -1 : (int) tag; + } + + public void setStartActualHeight(int actualWidth) { + mTransformedView.setTag(START_ACTUAL_HEIGHT, actualWidth); + } + + @Override + public void recycle() { + super.recycle(); + if (getClass() == MessagingImageTransformState.class) { + sInstancePool.release(this); + } + } + + @Override + protected void resetTransformedView() { + super.resetTransformedView(); + mImageMessage.setActualWidth(mImageMessage.getStaticWidth()); + mImageMessage.setActualHeight(mImageMessage.getHeight()); + } + + @Override + protected void reset() { + super.reset(); + mImageMessage = null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java new file mode 100644 index 000000000000..314a31d336fd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java @@ -0,0 +1,433 @@ +/* + * 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.notification; + +import android.content.res.Resources; +import android.util.Pools; +import android.view.View; +import android.view.ViewGroup; + +import com.android.internal.widget.MessagingGroup; +import com.android.internal.widget.MessagingImageMessage; +import com.android.internal.widget.MessagingLayout; +import com.android.internal.widget.MessagingLinearLayout; +import com.android.internal.widget.MessagingMessage; +import com.android.internal.widget.MessagingPropertyAnimator; +import com.android.systemui.Interpolators; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * A transform state of the action list +*/ +public class MessagingLayoutTransformState extends TransformState { + + private static Pools.SimplePool<MessagingLayoutTransformState> sInstancePool + = new Pools.SimplePool<>(40); + private MessagingLinearLayout mMessageContainer; + private MessagingLayout mMessagingLayout; + private HashMap<MessagingGroup, MessagingGroup> mGroupMap = new HashMap<>(); + private float mRelativeTranslationOffset; + + public static MessagingLayoutTransformState obtain() { + MessagingLayoutTransformState instance = sInstancePool.acquire(); + if (instance != null) { + return instance; + } + return new MessagingLayoutTransformState(); + } + + @Override + public void initFrom(View view, TransformInfo transformInfo) { + super.initFrom(view, transformInfo); + if (mTransformedView instanceof MessagingLinearLayout) { + mMessageContainer = (MessagingLinearLayout) mTransformedView; + mMessagingLayout = mMessageContainer.getMessagingLayout(); + Resources resources = view.getContext().getResources(); + mRelativeTranslationOffset = resources.getDisplayMetrics().density * 8; + } + } + + @Override + public boolean transformViewTo(TransformState otherState, float transformationAmount) { + if (otherState instanceof MessagingLayoutTransformState) { + // It's a party! Let's transform between these two layouts! + transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount, + true /* to */); + return true; + } else { + return super.transformViewTo(otherState, transformationAmount); + } + } + + @Override + public void transformViewFrom(TransformState otherState, float transformationAmount) { + if (otherState instanceof MessagingLayoutTransformState) { + // It's a party! Let's transform between these two layouts! + transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount, + false /* to */); + } else { + super.transformViewFrom(otherState, transformationAmount); + } + } + + private void transformViewInternal(MessagingLayoutTransformState mlt, + float transformationAmount, boolean to) { + ensureVisible(); + ArrayList<MessagingGroup> ownGroups = filterHiddenGroups( + mMessagingLayout.getMessagingGroups()); + ArrayList<MessagingGroup> otherGroups = filterHiddenGroups( + mlt.mMessagingLayout.getMessagingGroups()); + HashMap<MessagingGroup, MessagingGroup> pairs = findPairs(ownGroups, otherGroups); + MessagingGroup lastPairedGroup = null; + float currentTranslation = 0; + float transformationDistanceRemaining = 0; + for (int i = ownGroups.size() - 1; i >= 0; i--) { + MessagingGroup ownGroup = ownGroups.get(i); + MessagingGroup matchingGroup = pairs.get(ownGroup); + if (!isGone(ownGroup)) { + if (matchingGroup != null) { + transformGroups(ownGroup, matchingGroup, transformationAmount, to); + if (lastPairedGroup == null) { + lastPairedGroup = ownGroup; + if (to){ + float totalTranslation = ownGroup.getTop() - matchingGroup.getTop(); + transformationDistanceRemaining + = matchingGroup.getAvatar().getTranslationY(); + currentTranslation = transformationDistanceRemaining - totalTranslation; + } else { + float totalTranslation = matchingGroup.getTop() - ownGroup.getTop(); + currentTranslation = ownGroup.getAvatar().getTranslationY(); + transformationDistanceRemaining = currentTranslation - totalTranslation; + } + } + } else { + float groupTransformationAmount = transformationAmount; + if (lastPairedGroup != null) { + adaptGroupAppear(ownGroup, transformationAmount, currentTranslation, + to); + int distance = lastPairedGroup.getTop() - ownGroup.getTop(); + float transformationDistance = mTransformInfo.isAnimating() + ? distance + : ownGroup.getHeight() * 0.75f; + float translationProgress = transformationDistanceRemaining + - (distance - transformationDistance); + groupTransformationAmount = + translationProgress / transformationDistance; + groupTransformationAmount = Math.max(0.0f, Math.min(1.0f, + groupTransformationAmount)); + if (to) { + groupTransformationAmount = 1.0f - groupTransformationAmount; + } + } + if (to) { + disappear(ownGroup, groupTransformationAmount); + } else { + appear(ownGroup, groupTransformationAmount); + } + } + } + } + } + + private void appear(MessagingGroup ownGroup, float transformationAmount) { + MessagingLinearLayout ownMessages = ownGroup.getMessageContainer(); + for (int j = 0; j < ownMessages.getChildCount(); j++) { + View child = ownMessages.getChildAt(j); + if (isGone(child)) { + continue; + } + appear(child, transformationAmount); + setClippingDeactivated(child, true); + } + appear(ownGroup.getAvatar(), transformationAmount); + appear(ownGroup.getSenderView(), transformationAmount); + appear(ownGroup.getIsolatedMessage(), transformationAmount); + setClippingDeactivated(ownGroup.getSenderView(), true); + setClippingDeactivated(ownGroup.getAvatar(), true); + } + + private void adaptGroupAppear(MessagingGroup ownGroup, float transformationAmount, + float overallTranslation, boolean to) { + float relativeOffset; + if (to) { + relativeOffset = transformationAmount * mRelativeTranslationOffset; + } else { + relativeOffset = (1.0f - transformationAmount) * mRelativeTranslationOffset; + } + if (ownGroup.getSenderView().getVisibility() != View.GONE) { + relativeOffset *= 0.5f; + } + ownGroup.getMessageContainer().setTranslationY(relativeOffset); + ownGroup.setTranslationY(overallTranslation * 0.85f); + } + + private void disappear(MessagingGroup ownGroup, float transformationAmount) { + MessagingLinearLayout ownMessages = ownGroup.getMessageContainer(); + for (int j = 0; j < ownMessages.getChildCount(); j++) { + View child = ownMessages.getChildAt(j); + if (isGone(child)) { + continue; + } + disappear(child, transformationAmount); + setClippingDeactivated(child, true); + } + disappear(ownGroup.getAvatar(), transformationAmount); + disappear(ownGroup.getSenderView(), transformationAmount); + disappear(ownGroup.getIsolatedMessage(), transformationAmount); + setClippingDeactivated(ownGroup.getSenderView(), true); + setClippingDeactivated(ownGroup.getAvatar(), true); + } + + private void appear(View child, float transformationAmount) { + if (child == null || child.getVisibility() == View.GONE) { + return; + } + TransformState ownState = TransformState.createFrom(child, mTransformInfo); + ownState.appear(transformationAmount, null); + ownState.recycle(); + } + + private void disappear(View child, float transformationAmount) { + if (child == null || child.getVisibility() == View.GONE) { + return; + } + TransformState ownState = TransformState.createFrom(child, mTransformInfo); + ownState.disappear(transformationAmount, null); + ownState.recycle(); + } + + private ArrayList<MessagingGroup> filterHiddenGroups( + ArrayList<MessagingGroup> groups) { + ArrayList<MessagingGroup> result = new ArrayList<>(groups); + for (int i = 0; i < result.size(); i++) { + MessagingGroup messagingGroup = result.get(i); + if (isGone(messagingGroup)) { + result.remove(i); + i--; + } + } + return result; + } + + private void transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup, + float transformationAmount, boolean to) { + boolean useLinearTransformation = + otherGroup.getIsolatedMessage() == null && !mTransformInfo.isAnimating(); + transformView(transformationAmount, to, ownGroup.getSenderView(), otherGroup.getSenderView(), + true /* sameAsAny */, useLinearTransformation); + transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(), + true /* sameAsAny */, useLinearTransformation); + List<MessagingMessage> ownMessages = ownGroup.getMessages(); + List<MessagingMessage> otherMessages = otherGroup.getMessages(); + float previousTranslation = 0; + for (int i = 0; i < ownMessages.size(); i++) { + View child = ownMessages.get(ownMessages.size() - 1 - i).getView(); + if (isGone(child)) { + continue; + } + int otherIndex = otherMessages.size() - 1 - i; + View otherChild = null; + if (otherIndex >= 0) { + otherChild = otherMessages.get(otherIndex).getView(); + if (isGone(otherChild)) { + otherChild = null; + } + } + if (otherChild == null) { + float distanceToTop = child.getTop() + child.getHeight() + previousTranslation; + transformationAmount = distanceToTop / child.getHeight(); + transformationAmount = Math.max(0.0f, Math.min(1.0f, transformationAmount)); + if (to) { + transformationAmount = 1.0f - transformationAmount; + } + } + transformView(transformationAmount, to, child, otherChild, false, /* sameAsAny */ + useLinearTransformation); + if (transformationAmount == 0.0f + && otherGroup.getIsolatedMessage() == otherChild) { + ownGroup.setTransformingImages(true); + } + if (otherChild == null) { + child.setTranslationY(previousTranslation); + setClippingDeactivated(child, true); + } else if (to) { + float totalTranslation = child.getTop() + ownGroup.getTop() + - otherChild.getTop() - otherChild.getTop(); + previousTranslation = otherChild.getTranslationY() - totalTranslation; + } else { + previousTranslation = child.getTranslationY(); + } + } + ownGroup.updateClipRect(); + } + + private void transformView(float transformationAmount, boolean to, View ownView, + View otherView, boolean sameAsAny, boolean useLinearTransformation) { + TransformState ownState = TransformState.createFrom(ownView, mTransformInfo); + if (useLinearTransformation) { + ownState.setDefaultInterpolator(Interpolators.LINEAR); + } + ownState.setIsSameAsAnyView(sameAsAny); + if (to) { + if (otherView != null) { + TransformState otherState = TransformState.createFrom(otherView, mTransformInfo); + ownState.transformViewTo(otherState, transformationAmount); + otherState.recycle(); + } else { + ownState.disappear(transformationAmount, null); + } + } else { + if (otherView != null) { + TransformState otherState = TransformState.createFrom(otherView, mTransformInfo); + ownState.transformViewFrom(otherState, transformationAmount); + otherState.recycle(); + } else { + ownState.appear(transformationAmount, null); + } + } + ownState.recycle(); + } + + private HashMap<MessagingGroup, MessagingGroup> findPairs(ArrayList<MessagingGroup> ownGroups, + ArrayList<MessagingGroup> otherGroups) { + mGroupMap.clear(); + int lastMatch = Integer.MAX_VALUE; + for (int i = ownGroups.size() - 1; i >= 0; i--) { + MessagingGroup ownGroup = ownGroups.get(i); + MessagingGroup bestMatch = null; + int bestCompatibility = 0; + for (int j = Math.min(otherGroups.size(), lastMatch) - 1; j >= 0; j--) { + MessagingGroup otherGroup = otherGroups.get(j); + int compatibility = ownGroup.calculateGroupCompatibility(otherGroup); + if (compatibility > bestCompatibility) { + bestCompatibility = compatibility; + bestMatch = otherGroup; + lastMatch = j; + } + } + if (bestMatch != null) { + mGroupMap.put(ownGroup, bestMatch); + } + } + return mGroupMap; + } + + private boolean isGone(View view) { + if (view.getVisibility() == View.GONE) { + return true; + } + final ViewGroup.LayoutParams lp = view.getLayoutParams(); + if (lp instanceof MessagingLinearLayout.LayoutParams + && ((MessagingLinearLayout.LayoutParams) lp).hide) { + return true; + } + return false; + } + + @Override + public void setVisible(boolean visible, boolean force) { + super.setVisible(visible, force); + resetTransformedView(); + ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups(); + for (int i = 0; i < ownGroups.size(); i++) { + MessagingGroup ownGroup = ownGroups.get(i); + if (!isGone(ownGroup)) { + MessagingLinearLayout ownMessages = ownGroup.getMessageContainer(); + for (int j = 0; j < ownMessages.getChildCount(); j++) { + View child = ownMessages.getChildAt(j); + setVisible(child, visible, force); + } + setVisible(ownGroup.getAvatar(), visible, force); + setVisible(ownGroup.getSenderView(), visible, force); + MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage(); + if (isolatedMessage != null) { + setVisible(isolatedMessage, visible, force); + } + } + } + } + + private void setVisible(View child, boolean visible, boolean force) { + if (isGone(child) || MessagingPropertyAnimator.isAnimatingAlpha(child)) { + return; + } + TransformState ownState = TransformState.createFrom(child, mTransformInfo); + ownState.setVisible(visible, force); + ownState.recycle(); + } + + @Override + protected void resetTransformedView() { + super.resetTransformedView(); + ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups(); + for (int i = 0; i < ownGroups.size(); i++) { + MessagingGroup ownGroup = ownGroups.get(i); + if (!isGone(ownGroup)) { + MessagingLinearLayout ownMessages = ownGroup.getMessageContainer(); + for (int j = 0; j < ownMessages.getChildCount(); j++) { + View child = ownMessages.getChildAt(j); + if (isGone(child)) { + continue; + } + resetTransformedView(child); + setClippingDeactivated(child, false); + } + resetTransformedView(ownGroup.getAvatar()); + resetTransformedView(ownGroup.getSenderView()); + MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage(); + if (isolatedMessage != null) { + resetTransformedView(isolatedMessage); + } + setClippingDeactivated(ownGroup.getAvatar(), false); + setClippingDeactivated(ownGroup.getSenderView(), false); + ownGroup.setTranslationY(0); + ownGroup.getMessageContainer().setTranslationY(0); + } + ownGroup.setTransformingImages(false); + ownGroup.updateClipRect(); + } + } + + @Override + public void prepareFadeIn() { + super.prepareFadeIn(); + setVisible(true /* visible */, false /* force */); + } + + private void resetTransformedView(View child) { + TransformState ownState = TransformState.createFrom(child, mTransformInfo); + ownState.resetTransformedView(); + ownState.recycle(); + } + + @Override + protected void reset() { + super.reset(); + mMessageContainer = null; + mMessagingLayout = null; + } + + @Override + public void recycle() { + super.recycle(); + mGroupMap.clear();; + sInstancePool.release(this); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java index bca4b43afc0c..adc091457364 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java @@ -115,4 +115,9 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { super.setLegacy(legacy); mIsLegacy = legacy; } + + @Override + public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) { + return true; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java index eb211a10737b..548f006c934d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java @@ -58,6 +58,11 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi @Override public boolean isDimmable() { - return false; + return getCustomBackgroundColor() == 0; + } + + @Override + public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) { + return true; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java index f6ee1cac9c3d..fb5644f76ce3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java @@ -16,7 +16,9 @@ package com.android.systemui.statusbar.notification; +import com.android.internal.widget.MessagingLayout; import com.android.internal.widget.MessagingLinearLayout; +import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.TransformableView; @@ -32,41 +34,20 @@ import java.util.ArrayList; */ public class NotificationMessagingTemplateViewWrapper extends NotificationTemplateViewWrapper { - private View mContractedMessage; - private ArrayList<View> mHistoricMessages = new ArrayList<View>(); + private final int mMinHeightWithActions; + private MessagingLayout mMessagingLayout; + private MessagingLinearLayout mMessagingLinearLayout; protected NotificationMessagingTemplateViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { super(ctx, view, row); + mMessagingLayout = (MessagingLayout) view; + mMinHeightWithActions = NotificationUtils.getFontScaledHeight(ctx, + R.dimen.notification_messaging_actions_min_height); } private void resolveViews() { - mContractedMessage = null; - - View container = mView.findViewById(com.android.internal.R.id.notification_messaging); - if (container instanceof MessagingLinearLayout - && ((MessagingLinearLayout) container).getChildCount() > 0) { - MessagingLinearLayout messagingContainer = (MessagingLinearLayout) container; - - int childCount = messagingContainer.getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = messagingContainer.getChildAt(i); - - if (child.getVisibility() == View.GONE - && child instanceof TextView - && !TextUtils.isEmpty(((TextView) child).getText())) { - mHistoricMessages.add(child); - } - - // Only consider the first visible child - transforming to a position other than the - // first looks bad because we have to move across other messages that are fading in. - if (child.getId() == messagingContainer.getContractedChildId()) { - mContractedMessage = child; - } else if (child.getVisibility() == View.VISIBLE) { - break; - } - } - } + mMessagingLinearLayout = mMessagingLayout.getMessagingLinearLayout(); } @Override @@ -81,16 +62,22 @@ public class NotificationMessagingTemplateViewWrapper extends NotificationTempla protected void updateTransformedTypes() { // This also clears the existing types super.updateTransformedTypes(); - if (mContractedMessage != null) { - mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT, - mContractedMessage); + if (mMessagingLinearLayout != null) { + mTransformationHelper.addTransformedView(mMessagingLinearLayout.getId(), + mMessagingLinearLayout); } } @Override public void setRemoteInputVisible(boolean visible) { - for (int i = 0; i < mHistoricMessages.size(); i++) { - mHistoricMessages.get(i).setVisibility(visible ? View.VISIBLE : View.GONE); + mMessagingLayout.showHistoricMessages(visible); + } + + @Override + public int getMinLayoutHeight() { + if (mActionsContainer != null && mActionsContainer.getVisibility() != View.GONE) { + return mMinHeightWithActions; } + return super.getMinLayoutHeight(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java index bb979ebd1288..d463eae6e43f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java @@ -41,7 +41,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp private ProgressBar mProgressBar; private TextView mTitle; private TextView mText; - private View mActionsContainer; + protected View mActionsContainer; private View mReplyAction; private Rect mTmpRect = new Rect(); @@ -265,6 +265,15 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp updateActionOffset(); } + @Override + public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) { + if (super.shouldClipToRounding(topRounded, bottomRounded)) { + return true; + } + return bottomRounded && mActionsContainer != null + && mActionsContainer.getVisibility() != View.GONE; + } + private void updateActionOffset() { if (mActionsContainer != null) { // We should never push the actions higher than they are in the headsup view. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java index 3115361c6927..7e2336c4454b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java @@ -61,9 +61,14 @@ public class NotificationUtils { return sLocationOffset[1] - sLocationBase[1]; } - public static boolean isHapticFeedbackDisabled(Context context) { - return Settings.System.getIntForUser(context.getContentResolver(), - Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0; + /** + * @param dimenId the dimen to look up + * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp + */ + public static int getFontScaledHeight(Context context, int dimenId) { + int dimensionPixelSize = context.getResources().getDimensionPixelSize(dimenId); + float factor = Math.max(1.0f, context.getResources().getDisplayMetrics().scaledDensity / + context.getResources().getDisplayMetrics().density); + return (int) (dimensionPixelSize * factor); } - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java index 5200d6962ed5..17eb4c110031 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java @@ -190,4 +190,12 @@ public abstract class NotificationViewWrapper implements TransformableView { public boolean disallowSingleClick(float x, float y) { return false; } + + public int getMinLayoutHeight() { + return 0; + } + + public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) { + return false; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java index 80ba94398e1b..92dcc9e3cf9f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java @@ -34,6 +34,19 @@ import com.android.systemui.statusbar.stack.ViewState; */ public class PropertyAnimator { + public static <T extends View> void setProperty(final T view, + AnimatableProperty animatableProperty, float newEndValue, + AnimationProperties properties, boolean animated) { + int animatorTag = animatableProperty.getAnimatorTag(); + ValueAnimator previousAnimator = ViewState.getChildTag(view, animatorTag); + if (previousAnimator != null || animated) { + startAnimation(view, animatableProperty, newEndValue, properties); + } else { + // no new animation needed, let's just apply the value + animatableProperty.getProperty().set(view, newEndValue); + } + } + public static <T extends View> void startAnimation(final T view, AnimatableProperty animatableProperty, float newEndValue, AnimationProperties properties) { @@ -102,10 +115,4 @@ public class PropertyAnimator { view.setTag(animationEndTag, newEndValue); } - public interface AnimatableProperty { - int getAnimationStartTag(); - int getAnimationEndTag(); - int getAnimatorTag(); - Property getProperty(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java index 3491f8121545..c2141714b391 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification; import android.content.Context; import android.support.v4.view.AsyncLayoutInflater; +import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -30,15 +31,23 @@ import com.android.systemui.statusbar.NotificationData; * An inflater task that asynchronously inflates a ExpandableNotificationRow */ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInflateFinishedListener { + + private static final String TAG = "RowInflaterTask"; + private static final boolean TRACE_ORIGIN = true; + private RowInflationFinishedListener mListener; private NotificationData.Entry mEntry; private boolean mCancelled; + private Throwable mInflateOrigin; /** * Inflates a new notificationView. This should not be called twice on this object */ public void inflate(Context context, ViewGroup parent, NotificationData.Entry entry, RowInflationFinishedListener listener) { + if (TRACE_ORIGIN) { + mInflateOrigin = new Throwable("inflate requested here"); + } mListener = listener; AsyncLayoutInflater inflater = new AsyncLayoutInflater(context); mEntry = entry; @@ -54,8 +63,16 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf @Override public void onInflateFinished(View view, int resid, ViewGroup parent) { if (!mCancelled) { - mEntry.onInflationTaskFinished(); - mListener.onInflationFinished((ExpandableNotificationRow) view); + try { + mEntry.onInflationTaskFinished(); + mListener.onInflationFinished((ExpandableNotificationRow) view); + } catch (Throwable t) { + if (mInflateOrigin != null) { + Log.e(TAG, "Error in inflation finished listener: " + t, mInflateOrigin); + t.addSuppressed(mInflateOrigin); + } + throw t; + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java index c4aabe48f61c..178c8130307f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java @@ -33,8 +33,8 @@ public class TextViewTransformState extends TransformState { private TextView mText; @Override - public void initFrom(View view) { - super.initFrom(view); + public void initFrom(View view, TransformInfo transformInfo) { + super.initFrom(view, transformInfo); if (view instanceof TextView) { mText = (TextView) view; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java index bafedff4f78c..fc8ceb6620a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java @@ -26,6 +26,9 @@ import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; +import com.android.internal.widget.MessagingImageMessage; +import com.android.internal.widget.MessagingPropertyAnimator; +import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; @@ -43,23 +46,46 @@ public class TransformState { public static final int TRANSFORM_ALL = TRANSFORM_X | TRANSFORM_Y; private static final float UNDEFINED = -1f; - private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag; - private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag; - private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag; private static final int TRANSFORMATION_START_X = R.id.transformation_start_x_tag; private static final int TRANSFORMATION_START_Y = R.id.transformation_start_y_tag; private static final int TRANSFORMATION_START_SCLALE_X = R.id.transformation_start_scale_x_tag; private static final int TRANSFORMATION_START_SCLALE_Y = R.id.transformation_start_scale_y_tag; private static Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40); + private static ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS + = new ViewClippingUtil.ClippingParameters() { + @Override + public boolean shouldFinish(View view) { + if (view instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) view; + return !row.isChildInGroup(); + } + return false; + } + + @Override + public void onClippingStateChanged(View view, boolean isClipping) { + if (view instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (isClipping) { + row.setClipToActualHeight(true); + } else if (row.isChildInGroup()) { + row.setClipToActualHeight(false); + } + } + } + }; protected View mTransformedView; + protected TransformInfo mTransformInfo; private int[] mOwnPosition = new int[2]; private boolean mSameAsAny; private float mTransformationEndY = UNDEFINED; private float mTransformationEndX = UNDEFINED; + protected Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN; - public void initFrom(View view) { + public void initFrom(View view, TransformInfo transformInfo) { mTransformedView = view; + mTransformInfo = transformInfo; } /** @@ -70,18 +96,22 @@ public class TransformState { public void transformViewFrom(TransformState otherState, float transformationAmount) { mTransformedView.animate().cancel(); if (sameAs(otherState)) { - if (mTransformedView.getVisibility() == View.INVISIBLE - || mTransformedView.getAlpha() != 1.0f) { - // We have the same content, lets show ourselves - mTransformedView.setAlpha(1.0f); - mTransformedView.setVisibility(View.VISIBLE); - } + ensureVisible(); } else { CrossFadeHelper.fadeIn(mTransformedView, transformationAmount); } transformViewFullyFrom(otherState, transformationAmount); } + protected void ensureVisible() { + if (mTransformedView.getVisibility() == View.INVISIBLE + || mTransformedView.getAlpha() != 1.0f) { + // We have the same content, lets show ourselves + mTransformedView.setAlpha(1.0f); + mTransformedView.setVisibility(View.VISIBLE); + } + } + public void transformViewFullyFrom(TransformState otherState, float transformationAmount) { transformViewFrom(otherState, TRANSFORM_ALL, null, transformationAmount); } @@ -102,19 +132,26 @@ public class TransformState { transformViewFrom(otherState, TRANSFORM_Y, null, transformationAmount); } - private void transformViewFrom(TransformState otherState, int transformationFlags, + protected void transformViewFrom(TransformState otherState, int transformationFlags, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount) { final View transformedView = mTransformedView; boolean transformX = (transformationFlags & TRANSFORM_X) != 0; boolean transformY = (transformationFlags & TRANSFORM_Y) != 0; - boolean transformScale = transformScale(otherState); + int viewHeight = getViewHeight(); + int otherHeight = otherState.getViewHeight(); + boolean differentHeight = otherHeight != viewHeight && otherHeight != 0 && viewHeight != 0; + int viewWidth = getViewWidth(); + int otherWidth = otherState.getViewWidth(); + boolean differentWidth = otherWidth != viewWidth && otherWidth != 0 && viewWidth != 0; + boolean transformScale = transformScale(otherState) && (differentHeight || differentWidth); // lets animate the positions correctly if (transformationAmount == 0.0f || transformX && getTransformationStartX() == UNDEFINED || transformY && getTransformationStartY() == UNDEFINED - || transformScale && getTransformationStartScaleX() == UNDEFINED - || transformScale && getTransformationStartScaleY() == UNDEFINED) { + || transformScale && getTransformationStartScaleX() == UNDEFINED && differentWidth + || transformScale && getTransformationStartScaleY() == UNDEFINED + && differentHeight) { int[] otherPosition; if (transformationAmount != 0.0f) { otherPosition = otherState.getLaidOutLocationOnScreen(); @@ -132,16 +169,16 @@ public class TransformState { } // we also want to animate the scale if we're the same View otherView = otherState.getTransformedView(); - if (transformScale && otherState.getViewWidth() != getViewWidth()) { - setTransformationStartScaleX(otherState.getViewWidth() * otherView.getScaleX() - / (float) getViewWidth()); + if (transformScale && differentWidth) { + setTransformationStartScaleX(otherWidth * otherView.getScaleX() + / (float) viewWidth); transformedView.setPivotX(0); } else { setTransformationStartScaleX(UNDEFINED); } - if (transformScale && otherState.getViewHeight() != getViewHeight()) { - setTransformationStartScaleY(otherState.getViewHeight() * otherView.getScaleY() - / (float) getViewHeight()); + if (transformScale && differentHeight) { + setTransformationStartScaleY(otherHeight * otherView.getScaleY() + / (float) viewHeight); transformedView.setPivotY(0); } else { setTransformationStartScaleY(UNDEFINED); @@ -159,7 +196,7 @@ public class TransformState { } setClippingDeactivated(transformedView, true); } - float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( + float interpolatedValue = mDefaultInterpolator.getInterpolation( transformationAmount); if (transformX) { float interpolation = interpolatedValue; @@ -297,7 +334,7 @@ public class TransformState { } setClippingDeactivated(transformedView, true); } - float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( + float interpolatedValue = mDefaultInterpolator.getInterpolation( transformationAmount); int[] otherStablePosition = otherState.getLaidOutLocationOnScreen(); int[] ownPosition = getLaidOutLocationOnScreen(); @@ -354,59 +391,8 @@ public class TransformState { } } - public static void setClippingDeactivated(final View transformedView, boolean deactivated) { - if (!(transformedView.getParent() instanceof ViewGroup)) { - return; - } - ViewGroup view = (ViewGroup) transformedView.getParent(); - while (true) { - ArraySet<View> clipSet = (ArraySet<View>) view.getTag(CLIP_CLIPPING_SET); - if (clipSet == null) { - clipSet = new ArraySet<>(); - view.setTag(CLIP_CLIPPING_SET, clipSet); - } - Boolean clipChildren = (Boolean) view.getTag(CLIP_CHILDREN_TAG); - if (clipChildren == null) { - clipChildren = view.getClipChildren(); - view.setTag(CLIP_CHILDREN_TAG, clipChildren); - } - Boolean clipToPadding = (Boolean) view.getTag(CLIP_TO_PADDING); - if (clipToPadding == null) { - clipToPadding = view.getClipToPadding(); - view.setTag(CLIP_TO_PADDING, clipToPadding); - } - ExpandableNotificationRow row = view instanceof ExpandableNotificationRow - ? (ExpandableNotificationRow) view - : null; - if (!deactivated) { - clipSet.remove(transformedView); - if (clipSet.isEmpty()) { - view.setClipChildren(clipChildren); - view.setClipToPadding(clipToPadding); - view.setTag(CLIP_CLIPPING_SET, null); - if (row != null) { - row.setClipToActualHeight(true); - } - } - } else { - clipSet.add(transformedView); - view.setClipChildren(false); - view.setClipToPadding(false); - if (row != null && row.isChildInGroup()) { - // We still want to clip to the parent's height - row.setClipToActualHeight(false); - } - } - if (row != null && !row.isChildInGroup()) { - return; - } - final ViewParent parent = view.getParent(); - if (parent instanceof ViewGroup) { - view = (ViewGroup) parent; - } else { - return; - } - } + protected void setClippingDeactivated(final View transformedView, boolean deactivated) { + ViewClippingUtil.setClippingDeactivated(transformedView, deactivated, CLIPPING_PARAMETERS); } public int[] getLaidOutLocationOnScreen() { @@ -423,6 +409,9 @@ public class TransformState { // remove scale mOwnPosition[0] -= (1.0f - mTransformedView.getScaleX()) * mTransformedView.getPivotX(); mOwnPosition[1] -= (1.0f - mTransformedView.getScaleY()) * mTransformedView.getPivotY(); + + // Remove local translations + mOwnPosition[1] -= MessagingPropertyAnimator.getLocalTranslationY(mTransformedView); return mOwnPosition; } @@ -444,20 +433,31 @@ public class TransformState { CrossFadeHelper.fadeOut(mTransformedView, transformationAmount); } - public static TransformState createFrom(View view) { + public static TransformState createFrom(View view, + TransformInfo transformInfo) { if (view instanceof TextView) { TextViewTransformState result = TextViewTransformState.obtain(); - result.initFrom(view); + result.initFrom(view, transformInfo); return result; } if (view.getId() == com.android.internal.R.id.actions_container) { ActionListTransformState result = ActionListTransformState.obtain(); - result.initFrom(view); + result.initFrom(view, transformInfo); + return result; + } + if (view.getId() == com.android.internal.R.id.notification_messaging) { + MessagingLayoutTransformState result = MessagingLayoutTransformState.obtain(); + result.initFrom(view, transformInfo); + return result; + } + if (view instanceof MessagingImageMessage) { + MessagingImageTransformState result = MessagingImageTransformState.obtain(); + result.initFrom(view, transformInfo); return result; } if (view instanceof ImageView) { ImageTransformState result = ImageTransformState.obtain(); - result.initFrom(view); + result.initFrom(view, transformInfo); if (view.getId() == com.android.internal.R.id.reply_icon_action) { ((TransformState) result).setIsSameAsAnyView(true); } @@ -465,15 +465,15 @@ public class TransformState { } if (view instanceof ProgressBar) { ProgressTransformState result = ProgressTransformState.obtain(); - result.initFrom(view); + result.initFrom(view, transformInfo); return result; } TransformState result = obtain(); - result.initFrom(view); + result.initFrom(view, transformInfo); return result; } - private void setIsSameAsAnyView(boolean sameAsAny) { + public void setIsSameAsAnyView(boolean sameAsAny) { mSameAsAny = sameAsAny; } @@ -533,6 +533,7 @@ public class TransformState { mSameAsAny = false; mTransformationEndX = UNDEFINED; mTransformationEndY = UNDEFINED; + mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN; } public void setVisible(boolean visible, boolean force) { @@ -578,4 +579,12 @@ public class TransformState { public View getTransformedView() { return mTransformedView; } + + public void setDefaultInterpolator(Interpolator interpolator) { + mDefaultInterpolator = interpolator; + } + + public interface TransformInfo { + boolean isAnimating(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index 1bd90fa9ca08..b220686b3056 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -14,16 +14,13 @@ package com.android.systemui.statusbar.phone; +import android.app.AlarmManager.AlarmClockInfo; import android.content.Context; import android.os.Handler; -import android.os.Looper; import android.provider.Settings.Secure; - import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.app.NightDisplayController; +import com.android.internal.app.ColorDisplayController; import com.android.systemui.Dependency; -import com.android.systemui.Prefs; -import com.android.systemui.Prefs.Key; import com.android.systemui.qs.AutoAddTracker; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.SecureSetting; @@ -31,27 +28,37 @@ import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DataSaverController.Listener; import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.statusbar.policy.HotspotController.Callback; +import com.android.systemui.statusbar.policy.NextAlarmController; +import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback; /** * Manages which tiles should be automatically added to QS. */ public class AutoTileManager { - public static final String HOTSPOT = "hotspot"; public static final String SAVER = "saver"; public static final String INVERSION = "inversion"; public static final String WORK = "work"; public static final String NIGHT = "night"; + public static final String ALARM = "alarm"; + private final Context mContext; private final QSTileHost mHost; private final Handler mHandler; private final AutoAddTracker mAutoTracker; public AutoTileManager(Context context, QSTileHost host) { - mAutoTracker = new AutoAddTracker(context); + this(context, new AutoAddTracker(context), host, + new Handler(Dependency.get(Dependency.BG_LOOPER))); + } + + @VisibleForTesting + AutoTileManager(Context context, AutoAddTracker autoAddTracker, QSTileHost host, + Handler handler) { + mAutoTracker = autoAddTracker; mContext = context; mHost = host; - mHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER)); + mHandler = handler; if (!mAutoTracker.isAdded(HOTSPOT)) { Dependency.get(HotspotController.class).addCallback(mHotspotCallback); } @@ -76,20 +83,25 @@ public class AutoTileManager { if (!mAutoTracker.isAdded(WORK)) { Dependency.get(ManagedProfileController.class).addCallback(mProfileCallback); } - if (!mAutoTracker.isAdded(NIGHT) - && NightDisplayController.isAvailable(mContext)) { - Dependency.get(NightDisplayController.class).setListener(mNightDisplayCallback); + && ColorDisplayController.isAvailable(mContext)) { + Dependency.get(ColorDisplayController.class).setListener(mColorDisplayCallback); + } + if (!mAutoTracker.isAdded(ALARM)) { + Dependency.get(NextAlarmController.class).addCallback(mNextAlarmChangeCallback); } } public void destroy() { - mColorsSetting.setListening(false); + if (mColorsSetting != null) { + mColorsSetting.setListening(false); + } mAutoTracker.destroy(); Dependency.get(HotspotController.class).removeCallback(mHotspotCallback); Dependency.get(DataSaverController.class).removeCallback(mDataSaverListener); Dependency.get(ManagedProfileController.class).removeCallback(mProfileCallback); - Dependency.get(NightDisplayController.class).setListener(null); + Dependency.get(ColorDisplayController.class).setListener(null); + Dependency.get(NextAlarmController.class).removeCallback(mNextAlarmChangeCallback); } private final ManagedProfileController.Callback mProfileCallback = @@ -127,7 +139,7 @@ public class AutoTileManager { private final HotspotController.Callback mHotspotCallback = new Callback() { @Override - public void onHotspotChanged(boolean enabled) { + public void onHotspotChanged(boolean enabled, int numDevices) { if (mAutoTracker.isAdded(HOTSPOT)) return; if (enabled) { mHost.addTile(HOTSPOT); @@ -138,9 +150,22 @@ public class AutoTileManager { } }; + private final NextAlarmChangeCallback mNextAlarmChangeCallback = new NextAlarmChangeCallback() { + @Override + public void onNextAlarmChanged(AlarmClockInfo nextAlarm) { + if (mAutoTracker.isAdded(ALARM)) return; + if (nextAlarm != null) { + mHost.addTile(ALARM); + mAutoTracker.setTileAdded(ALARM); + mHandler.post(() -> Dependency.get(NextAlarmController.class) + .removeCallback(mNextAlarmChangeCallback)); + } + } + }; + @VisibleForTesting - final NightDisplayController.Callback mNightDisplayCallback = - new NightDisplayController.Callback() { + final ColorDisplayController.Callback mColorDisplayCallback = + new ColorDisplayController.Callback() { @Override public void onActivated(boolean activated) { if (activated) { @@ -150,8 +175,8 @@ public class AutoTileManager { @Override public void onAutoModeChanged(int autoMode) { - if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM - || autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) { + if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM + || autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) { addNightTile(); } } @@ -160,7 +185,7 @@ public class AutoTileManager { if (mAutoTracker.isAdded(NIGHT)) return; mHost.addTile(NIGHT); mAutoTracker.setTileAdded(NIGHT); - mHandler.post(() -> Dependency.get(NightDisplayController.class) + mHandler.post(() -> Dependency.get(ColorDisplayController.class) .setListener(null)); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java index a83e6591c48b..7284ee8b39bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java @@ -14,7 +14,6 @@ package com.android.systemui.statusbar.phone; -import android.graphics.drawable.Drawable; import android.view.View; import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface; @@ -35,10 +34,12 @@ public class ButtonDispatcher { private View.OnClickListener mClickListener; private View.OnTouchListener mTouchListener; private View.OnLongClickListener mLongClickListener; + private View.OnHoverListener mOnHoverListener; private Boolean mLongClickable; private Integer mAlpha; private Float mDarkIntensity; private Integer mVisibility = -1; + private Boolean mDelayTouchFeedback; private KeyButtonDrawable mImageDrawable; private View mCurrentView; private boolean mVertical; @@ -56,6 +57,7 @@ public class ButtonDispatcher { view.setOnClickListener(mClickListener); view.setOnTouchListener(mTouchListener); view.setOnLongClickListener(mLongClickListener); + view.setOnHoverListener(mOnHoverListener); if (mLongClickable != null) { view.setLongClickable(mLongClickable); } @@ -71,10 +73,10 @@ public class ButtonDispatcher { if (mImageDrawable != null) { ((ButtonInterface) view).setImageDrawable(mImageDrawable); } - - if (view instanceof ButtonInterface) { - ((ButtonInterface) view).setVertical(mVertical); + if (mDelayTouchFeedback != null) { + ((ButtonInterface) view).setDelayTouchFeedback(mDelayTouchFeedback); } + ((ButtonInterface) view).setVertical(mVertical); } public int getId() { @@ -89,6 +91,10 @@ public class ButtonDispatcher { return mAlpha != null ? mAlpha : 1; } + public KeyButtonDrawable getImageDrawable() { + return mImageDrawable; + } + public void setImageDrawable(KeyButtonDrawable drawable) { mImageDrawable = drawable; final int N = mViews.size(); @@ -130,6 +136,14 @@ public class ButtonDispatcher { } } + public void setDelayTouchFeedback(boolean delay) { + mDelayTouchFeedback = delay; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + ((ButtonInterface) mViews.get(i)).setDelayTouchFeedback(delay); + } + } + public void setOnClickListener(View.OnClickListener clickListener) { mClickListener = clickListener; final int N = mViews.size(); @@ -162,6 +176,22 @@ public class ButtonDispatcher { } } + public void setOnHoverListener(View.OnHoverListener hoverListener) { + mOnHoverListener = hoverListener; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setOnHoverListener(mOnHoverListener); + } + } + + public void setClickable(boolean clickable) { + abortCurrentGesture(); + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setClickable(clickable); + } + } + public ArrayList<View> getViews() { return mViews; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java index 2c3f452e8274..f7f791ebaf63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java @@ -51,15 +51,19 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue public static final String TAG = "CollapsedStatusBarFragment"; private static final String EXTRA_PANEL_STATE = "panel_state"; + public static final int FADE_IN_DURATION = 320; + public static final int FADE_IN_DELAY = 50; private PhoneStatusBarView mStatusBar; private KeyguardMonitor mKeyguardMonitor; private NetworkController mNetworkController; private LinearLayout mSystemIconArea; + private View mClockView; private View mNotificationIconAreaInner; private int mDisabled1; private StatusBar mStatusBarComponent; private DarkIconManager mDarkIconManager; private SignalClusterView mSignalClusterView; + private View mOperatorNameFrame; private SignalCallback mSignalCallback = new SignalCallback() { @Override @@ -92,11 +96,13 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons)); Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager); mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area); + mClockView = mStatusBar.findViewById(R.id.clock); mSignalClusterView = mStatusBar.findViewById(R.id.signal_cluster); Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSignalClusterView); // Default to showing until we know otherwise. showSystemIconArea(false); initEmergencyCryptkeeperText(); + initOperatorName(); } @Override @@ -150,8 +156,10 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue if ((diff1 & DISABLE_SYSTEM_INFO) != 0) { if ((state1 & DISABLE_SYSTEM_INFO) != 0) { hideSystemIconArea(animate); + hideOperatorName(animate); } else { showSystemIconArea(animate); + showOperatorName(animate); } } if ((diff1 & DISABLE_NOTIFICATION_ICONS) != 0) { @@ -193,10 +201,12 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue public void hideSystemIconArea(boolean animate) { animateHide(mSystemIconArea, animate); + animateHide(mClockView, animate); } public void showSystemIconArea(boolean animate) { animateShow(mSystemIconArea, animate); + animateShow(mClockView, animate); } public void hideNotificationIconArea(boolean animate) { @@ -207,6 +217,18 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue animateShow(mNotificationIconAreaInner, animate); } + public void hideOperatorName(boolean animate) { + if (mOperatorNameFrame != null) { + animateHide(mOperatorNameFrame, animate); + } + } + + public void showOperatorName(boolean animate) { + if (mOperatorNameFrame != null) { + animateShow(mOperatorNameFrame, animate); + } + } + /** * Hides a view. */ @@ -237,9 +259,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } v.animate() .alpha(1f) - .setDuration(320) + .setDuration(FADE_IN_DURATION) .setInterpolator(Interpolators.ALPHA_IN) - .setStartDelay(50) + .setStartDelay(FADE_IN_DELAY) // We need to clean up any pending end action from animateHide if we call // both hide and show in the same frame before the animation actually gets started. @@ -268,4 +290,11 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue parent.removeView(emergencyViewStub); } } + + private void initOperatorName() { + if (getResources().getBoolean(R.bool.config_showOperatorNameInStatusBar)) { + ViewStub stub = mStatusBar.findViewById(R.id.operator_name); + mOperatorNameFrame = stub.inflate(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java index dcb6a3801844..0d62703cbced 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java @@ -142,7 +142,7 @@ public class DoubleTapHelper { && Math.abs(event.getY() - mDownY) < mTouchSlop; } - private boolean isWithinDoubleTapSlop(MotionEvent event) { + public boolean isWithinDoubleTapSlop(MotionEvent event) { if (!mActivated) { // If we're not activated there's no double tap slop to satisfy. return true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 6b7397b3f8ca..fb3adf45df31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -25,11 +25,14 @@ import android.util.MathUtils; import android.util.SparseBooleanArray; import com.android.internal.hardware.AmbientDisplayConfiguration; +import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.doze.AlwaysOnDisplayPolicy; +import com.android.systemui.tuner.TunerService; import java.io.PrintWriter; -public class DozeParameters { +public class DozeParameters implements TunerService.Tunable { private static final int MAX_DURATION = 60 * 1000; public static final String DOZE_SENSORS_WAKE_UP_FULLY = "doze_sensors_wake_up_fully"; @@ -37,19 +40,24 @@ public class DozeParameters { private final AmbientDisplayConfiguration mAmbientDisplayConfiguration; private static IntInOutMatcher sPickupSubtypePerformsProxMatcher; + private final AlwaysOnDisplayPolicy mAlwaysOnPolicy; + + private boolean mDozeAlwaysOn; public DozeParameters(Context context) { mContext = context; mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext); + mAlwaysOnPolicy = new AlwaysOnDisplayPolicy(context); + + Dependency.get(TunerService.class).addTunable(this, Settings.Secure.DOZE_ALWAYS_ON, + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED); } public void dump(PrintWriter pw) { pw.println(" DozeParameters:"); pw.print(" getDisplayStateSupported(): "); pw.println(getDisplayStateSupported()); - pw.print(" getPulseDuration(pickup=false): "); pw.println(getPulseDuration(false)); - pw.print(" getPulseDuration(pickup=true): "); pw.println(getPulseDuration(true)); - pw.print(" getPulseInDuration(pickup=false): "); pw.println(getPulseInDuration(false)); - pw.print(" getPulseInDuration(pickup=true): "); pw.println(getPulseInDuration(true)); + pw.print(" getPulseDuration(): "); pw.println(getPulseDuration()); + pw.print(" getPulseInDuration(): "); pw.println(getPulseInDuration()); pw.print(" getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration()); pw.print(" getPulseOutDuration(): "); pw.println(getPulseOutDuration()); pw.print(" getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion()); @@ -81,14 +89,17 @@ public class DozeParameters { return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported); } - public int getPulseDuration(boolean pickup) { - return getPulseInDuration(pickup) + getPulseVisibleDuration() + getPulseOutDuration(); + public int getPulseDuration() { + return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration(); + } + + public float getScreenBrightnessDoze() { + return mContext.getResources().getInteger( + com.android.internal.R.integer.config_screenBrightnessDoze) / 255f; } - public int getPulseInDuration(boolean pickupOrDoubleTap) { - return pickupOrDoubleTap - ? getInt("doze.pulse.duration.in.pickup", R.integer.doze_pulse_duration_in_pickup) - : getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in); + public int getPulseInDuration() { + return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in); } public int getPulseVisibleDuration() { @@ -119,8 +130,50 @@ public class DozeParameters { return getInt("doze.pickup.vibration.threshold", R.integer.doze_pickup_vibration_threshold); } + /** + * For how long a wallpaper can be visible in AoD before it fades aways. + * @return duration in millis. + */ + public long getWallpaperAodDuration() { + return mAlwaysOnPolicy.wallpaperVisibilityDuration; + } + + /** + * How long it takes for the wallpaper fade away (Animation duration.) + * @return duration in millis. + */ + public long getWallpaperFadeOutDuration() { + return mAlwaysOnPolicy.wallpaperFadeOutDuration; + } + + /** + * Checks if always on is available and enabled for the current user. + * @return {@code true} if enabled and available. + */ public boolean getAlwaysOn() { - return mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT); + return mDozeAlwaysOn; + } + + /** + * Some screens need to be completely black before changing the display power mode, + * unexpected behavior might happen if this parameter isn't respected. + * + * @return {@code true} if screen needs to be completely black before a power transition. + */ + public boolean getDisplayNeedsBlanking() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_displayBlanksAfterDoze); + } + + /** + * Whether we can implement our own screen off animation or if we need + * to rely on DisplayPowerManager to dim the display. + * + * @return {@code true} if SystemUI can control the screen off animation. + */ + public boolean getCanControlScreenOffAnimation() { + return !mContext.getResources().getBoolean( + com.android.internal.R.bool.config_dozeAfterScreenOff); } private boolean getBoolean(String propName, int resId) { @@ -161,6 +214,10 @@ public class DozeParameters { return mContext.getResources().getBoolean(R.bool.doze_double_tap_reports_touch_coordinates); } + @Override + public void onTuningChanged(String key, String newValue) { + mDozeAlwaysOn = mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT); + } /** * Parses a spec of the form `1,2,3,!5,*`. The resulting object will match numbers that are diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java index 8afb8490808e..1011383b72e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java @@ -16,16 +16,11 @@ package com.android.systemui.statusbar.phone; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.annotation.NonNull; import android.content.Context; import android.os.Handler; import android.util.Log; -import android.view.animation.Interpolator; -import com.android.systemui.Interpolators; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; @@ -40,74 +35,59 @@ public class DozeScrimController { private final Handler mHandler = new Handler(); private final ScrimController mScrimController; - private final Context mContext; - private boolean mDozing; private DozeHost.PulseCallback mPulseCallback; private int mPulseReason; - private Animator mInFrontAnimator; - private Animator mBehindAnimator; - private float mInFrontTarget; - private float mBehindTarget; - private boolean mDozingAborted; - private boolean mWakeAndUnlocking; private boolean mFullyPulsing; - private float mAodFrontScrimOpacity = 0; - private Runnable mSetDozeInFrontAlphaDelayed; + private final ScrimController.Callback mScrimCallback = new ScrimController.Callback() { + @Override + public void onDisplayBlanked() { + if (DEBUG) { + Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason=" + + DozeLog.pulseReasonToString(mPulseReason)); + } + if (!mDozing) { + return; + } + + // Signal that the pulse is ready to turn the screen on and draw. + pulseStarted(); + } + + @Override + public void onFinished() { + if (DEBUG) { + Log.d(TAG, "Pulse in finished, mDozing=" + mDozing); + } + if (!mDozing) { + return; + } + mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration()); + mHandler.postDelayed(mPulseOutExtended, + mDozeParameters.getPulseVisibleDurationExtended()); + mFullyPulsing = true; + } + + /** + * Transition was aborted before it was over. + */ + @Override + public void onCancelled() { + pulseFinished(); + } + }; public DozeScrimController(ScrimController scrimController, Context context) { - mContext = context; mScrimController = scrimController; mDozeParameters = new DozeParameters(context); } - public void setDozing(boolean dozing, boolean animate) { + public void setDozing(boolean dozing) { if (mDozing == dozing) return; mDozing = dozing; - mWakeAndUnlocking = false; - if (mDozing) { - mDozingAborted = false; - abortAnimations(); - mScrimController.setDozeBehindAlpha(1f); - setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() ? mAodFrontScrimOpacity : 1f); - } else { + if (!mDozing) { cancelPulsing(); - if (animate) { - startScrimAnimation(false /* inFront */, 0f /* target */, - NotificationPanelView.DOZE_ANIMATION_DURATION, - Interpolators.LINEAR_OUT_SLOW_IN); - startScrimAnimation(true /* inFront */, 0f /* target */, - NotificationPanelView.DOZE_ANIMATION_DURATION, - Interpolators.LINEAR_OUT_SLOW_IN); - } else { - abortAnimations(); - mScrimController.setDozeBehindAlpha(0f); - setDozeInFrontAlpha(0f); - } - } - } - - /** - * Set the opacity of the front scrim when showing AOD1 - * - * Used to emulate lower brightness values than the hardware supports natively. - */ - public void setAodDimmingScrim(float scrimOpacity) { - mAodFrontScrimOpacity = scrimOpacity; - if (mDozing && !isPulsing() && !mDozingAborted && !mWakeAndUnlocking - && mDozeParameters.getAlwaysOn()) { - setDozeInFrontAlpha(mAodFrontScrimOpacity); - } - } - - public void setWakeAndUnlocking() { - // Immediately abort the doze scrims in case of wake-and-unlock - // for pulsing so the Keyguard fade-out animation scrim can take over. - if (!mWakeAndUnlocking) { - mWakeAndUnlocking = true; - mScrimController.setDozeBehindAlpha(0f); - setDozeInFrontAlpha(0f); } } @@ -118,37 +98,21 @@ public class DozeScrimController { } if (!mDozing || mPulseCallback != null) { + if (DEBUG) { + Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? " + + (mPulseCallback != null)); + } // Pulse suppressed. callback.onPulseFinished(); return; } - // Begin pulse. Note that it's very important that the pulse finished callback + // Begin pulse. Note that it's very important that the pulse finished callback // be invoked when we're done so that the caller can drop the pulse wakelock. mPulseCallback = callback; mPulseReason = reason; - setDozeInFrontAlpha(1f); - mHandler.post(mPulseIn); - } - - /** - * Aborts pulsing immediately. - */ - public void abortPulsing() { - cancelPulsing(); - if (mDozing && !mWakeAndUnlocking) { - mScrimController.setDozeBehindAlpha(1f); - setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() && !mDozingAborted - ? mAodFrontScrimOpacity : 1f); - } - } - /** - * Aborts dozing immediately. - */ - public void abortDoze() { - mDozingAborted = true; - abortPulsing(); + mScrimController.transitionTo(ScrimState.PULSING, mScrimCallback); } public void pulseOutNow() { @@ -157,17 +121,6 @@ public class DozeScrimController { } } - public void onScreenTurnedOn() { - if (isPulsing()) { - final boolean pickupOrDoubleTap = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP - || mPulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP; - startScrimAnimation(true /* inFront */, 0f, - mDozeParameters.getPulseInDuration(pickupOrDoubleTap), - pickupOrDoubleTap ? Interpolators.LINEAR_OUT_SLOW_IN : Interpolators.ALPHA_OUT, - mPulseInFinished); - } - } - public boolean isPulsing() { return mPulseCallback != null; } @@ -181,11 +134,9 @@ public class DozeScrimController { } private void cancelPulsing() { - if (DEBUG) Log.d(TAG, "Cancel pulsing"); - if (mPulseCallback != null) { + if (DEBUG) Log.d(TAG, "Cancel pulsing"); mFullyPulsing = false; - mHandler.removeCallbacks(mPulseIn); mHandler.removeCallbacks(mPulseOut); mHandler.removeCallbacks(mPulseOutExtended); pulseFinished(); @@ -193,151 +144,20 @@ public class DozeScrimController { } private void pulseStarted() { + DozeLog.tracePulseStart(mPulseReason); if (mPulseCallback != null) { mPulseCallback.onPulseStarted(); } } private void pulseFinished() { + DozeLog.tracePulseFinish(); if (mPulseCallback != null) { mPulseCallback.onPulseFinished(); mPulseCallback = null; } } - private void abortAnimations() { - if (mInFrontAnimator != null) { - mInFrontAnimator.cancel(); - } - if (mBehindAnimator != null) { - mBehindAnimator.cancel(); - } - } - - private void startScrimAnimation(final boolean inFront, float target, long duration, - Interpolator interpolator) { - startScrimAnimation(inFront, target, duration, interpolator, null /* endRunnable */); - } - - private void startScrimAnimation(final boolean inFront, float target, long duration, - Interpolator interpolator, final Runnable endRunnable) { - Animator current = getCurrentAnimator(inFront); - if (current != null) { - float currentTarget = getCurrentTarget(inFront); - if (currentTarget == target) { - return; - } - current.cancel(); - } - ValueAnimator anim = ValueAnimator.ofFloat(getDozeAlpha(inFront), target); - anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (float) animation.getAnimatedValue(); - setDozeAlpha(inFront, value); - } - }); - anim.setInterpolator(interpolator); - anim.setDuration(duration); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - setCurrentAnimator(inFront, null); - if (endRunnable != null) { - endRunnable.run(); - } - } - }); - anim.start(); - setCurrentAnimator(inFront, anim); - setCurrentTarget(inFront, target); - } - - private float getCurrentTarget(boolean inFront) { - return inFront ? mInFrontTarget : mBehindTarget; - } - - private void setCurrentTarget(boolean inFront, float target) { - if (inFront) { - mInFrontTarget = target; - } else { - mBehindTarget = target; - } - } - - private Animator getCurrentAnimator(boolean inFront) { - return inFront ? mInFrontAnimator : mBehindAnimator; - } - - private void setCurrentAnimator(boolean inFront, Animator animator) { - if (inFront) { - mInFrontAnimator = animator; - } else { - mBehindAnimator = animator; - } - } - - private void setDozeAlpha(boolean inFront, float alpha) { - if (mWakeAndUnlocking) { - return; - } - if (inFront) { - mScrimController.setDozeInFrontAlpha(alpha); - } else { - mScrimController.setDozeBehindAlpha(alpha); - } - } - - private float getDozeAlpha(boolean inFront) { - return inFront - ? mScrimController.getDozeInFrontAlpha() - : mScrimController.getDozeBehindAlpha(); - } - - private void setDozeInFrontAlpha(float opacity) { - setDozeInFrontAlphaDelayed(opacity, 0 /* delay */); - - } - - private void setDozeInFrontAlphaDelayed(float opacity, long delayMs) { - if (mSetDozeInFrontAlphaDelayed != null) { - mHandler.removeCallbacks(mSetDozeInFrontAlphaDelayed); - mSetDozeInFrontAlphaDelayed = null; - } - if (delayMs <= 0) { - mScrimController.setDozeInFrontAlpha(opacity); - } else { - mHandler.postDelayed(mSetDozeInFrontAlphaDelayed = () -> { - setDozeInFrontAlpha(opacity); - }, delayMs); - } - } - - private final Runnable mPulseIn = new Runnable() { - @Override - public void run() { - if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason=" - + DozeLog.pulseReasonToString(mPulseReason)); - if (!mDozing) return; - DozeLog.tracePulseStart(mPulseReason); - - // Signal that the pulse is ready to turn the screen on and draw. - pulseStarted(); - } - }; - - private final Runnable mPulseInFinished = new Runnable() { - @Override - public void run() { - if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing); - if (!mDozing) return; - mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration()); - mHandler.postDelayed(mPulseOutExtended, - mDozeParameters.getPulseVisibleDurationExtended()); - mFullyPulsing = true; - } - }; - private final Runnable mPulseOutExtended = new Runnable() { @Override public void run() { @@ -354,38 +174,13 @@ public class DozeScrimController { mHandler.removeCallbacks(mPulseOutExtended); if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing); if (!mDozing) return; - startScrimAnimation(true /* inFront */, 1, - mDozeParameters.getPulseOutDuration(), - Interpolators.ALPHA_IN, mPulseOutFinishing); - } - }; - - private final Runnable mPulseOutFinishing = new Runnable() { - @Override - public void run() { - if (DEBUG) Log.d(TAG, "Pulse out finished"); - DozeLog.tracePulseFinish(); - if (mDozeParameters.getAlwaysOn() && mDozing) { - // Setting power states can block rendering. For AOD, delay finishing the pulse and - // setting the power state until the fully black scrim had time to hit the - // framebuffer. - mHandler.postDelayed(mPulseOutFinished, 30); - } else { - mPulseOutFinished.run(); - } - } - }; - - private final Runnable mPulseOutFinished = new Runnable() { - @Override - public void run() { - // Signal that the pulse is all finished so we can turn the screen off now. - DozeScrimController.this.pulseFinished(); - if (mDozeParameters.getAlwaysOn()) { - // Setting power states can happen after we push out the frame. Make sure we - // stay fully opaque until the power state request reaches the lower levels. - setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 100); - } + mScrimController.transitionTo(ScrimState.AOD, + new ScrimController.Callback() { + @Override + public void onDisplayBlanked() { + pulseFinished(); + } + }); } }; -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java index 91369dbd5f89..a2b1013167de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java @@ -26,7 +26,7 @@ import android.util.Log; import com.android.keyguard.KeyguardConstants; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; -import com.android.keyguard.LatencyTracker; +import com.android.internal.util.LatencyTracker; import com.android.systemui.Dependency; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; @@ -181,9 +181,9 @@ public class FingerprintUnlockController extends KeyguardUpdateMonitorCallback { } private boolean pulsingOrAod() { - boolean pulsing = mDozeScrimController.isPulsing(); - boolean dozingWithScreenOn = mStatusBar.isDozing() && !mStatusBar.isScreenFullyOff(); - return pulsing || dozingWithScreenOn; + final ScrimState scrimState = mScrimController.getState(); + return scrimState == ScrimState.AOD + || scrimState == ScrimState.PULSING; } @Override @@ -246,15 +246,12 @@ public class FingerprintUnlockController extends KeyguardUpdateMonitorCallback { true /* allowEnterAnimation */); } else if (mMode == MODE_WAKE_AND_UNLOCK){ Trace.beginSection("MODE_WAKE_AND_UNLOCK"); - mDozeScrimController.abortDoze(); } else { Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM"); mUpdateMonitor.awakenFromDream(); } mStatusBarWindowManager.setStatusBarFocusable(false); mKeyguardViewMediator.onWakeAndUnlocking(); - mScrimController.setWakeAndUnlocking(); - mDozeScrimController.setWakeAndUnlocking(); if (mStatusBar.getNavigationBarView() != null) { mStatusBar.getNavigationBarView().setWakeAndUnlocking(true); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java new file mode 100644 index 000000000000..aba5cdf0ca2b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -0,0 +1,446 @@ +/* + * 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.phone; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.support.v4.util.ArraySet; +import android.util.Log; +import android.util.Pools; +import android.view.View; +import android.view.ViewTreeObserver; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dumpable; +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.HeadsUpManager; +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashSet; +import java.util.Stack; + +/** + * A implementation of HeadsUpManager for phone and car. + */ +public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, + ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback, + OnHeadsUpChangedListener { + private static final String TAG = "HeadsUpManagerPhone"; + private static final boolean DEBUG = false; + + private final View mStatusBarWindowView; + private final int mStatusBarHeight; + private final NotificationGroupManager mGroupManager; + private final StatusBar mBar; + private final VisualStabilityManager mVisualStabilityManager; + + private boolean mReleaseOnExpandFinish; + private boolean mTrackingHeadsUp; + private HashSet<String> mSwipedOutKeys = new HashSet<>(); + private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>(); + private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed + = new ArraySet<>(); + private boolean mIsExpanded; + private int[] mTmpTwoArray = new int[2]; + private boolean mHeadsUpGoingAway; + private boolean mWaitingOnCollapseWhenGoingAway; + private boolean mIsObserving; + private int mStatusBarState; + + private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() { + private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>(); + + @Override + public HeadsUpEntryPhone acquire() { + if (!mPoolObjects.isEmpty()) { + return mPoolObjects.pop(); + } + return new HeadsUpEntryPhone(); + } + + @Override + public boolean release(@NonNull HeadsUpEntryPhone instance) { + mPoolObjects.push(instance); + return true; + } + }; + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Constructor: + + public HeadsUpManagerPhone(@NonNull final Context context, @NonNull View statusBarWindowView, + @NonNull NotificationGroupManager groupManager, @NonNull StatusBar bar, + @NonNull VisualStabilityManager visualStabilityManager) { + super(context); + + mStatusBarWindowView = statusBarWindowView; + mGroupManager = groupManager; + mBar = bar; + mVisualStabilityManager = visualStabilityManager; + + Resources resources = mContext.getResources(); + mStatusBarHeight = resources.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height); + + addListener(new OnHeadsUpChangedListener() { + @Override + public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) { + if (DEBUG) Log.w(TAG, "onHeadsUpPinnedModeChanged"); + updateTouchableRegionListener(); + } + }); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Public methods: + + /** + * Decides whether a click is invalid for a notification, i.e it has not been shown long enough + * that a user might have consciously clicked on it. + * + * @param key the key of the touched notification + * @return whether the touch is invalid and should be discarded + */ + public boolean shouldSwallowClick(@NonNull String key) { + HeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key); + return entry != null && mClock.currentTimeMillis() < entry.postTime; + } + + public void onExpandingFinished() { + if (mReleaseOnExpandFinish) { + releaseAllImmediately(); + mReleaseOnExpandFinish = false; + } else { + for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) { + if (isHeadsUp(entry.key)) { + // Maybe the heads-up was removed already + removeHeadsUpEntry(entry); + } + } + } + mEntriesToRemoveAfterExpand.clear(); + } + + /** + * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry + * from the list even after a Heads Up Notification is gone. + */ + public void setTrackingHeadsUp(boolean trackingHeadsUp) { + mTrackingHeadsUp = trackingHeadsUp; + } + + /** + * Notify that the status bar panel gets expanded or collapsed. + * + * @param isExpanded True to notify expanded, false to notify collapsed. + */ + public void setIsPanelExpanded(boolean isExpanded) { + if (isExpanded != mIsExpanded) { + mIsExpanded = isExpanded; + if (isExpanded) { + // make sure our state is sane + mWaitingOnCollapseWhenGoingAway = false; + mHeadsUpGoingAway = false; + updateTouchableRegionListener(); + } + } + } + + /** + * Set the current state of the statusbar. + */ + public void setStatusBarState(int statusBarState) { + mStatusBarState = statusBarState; + } + + /** + * Set that we are exiting the headsUp pinned mode, but some notifications might still be + * animating out. This is used to keep the touchable regions in a sane state. + */ + public void setHeadsUpGoingAway(boolean headsUpGoingAway) { + if (headsUpGoingAway != mHeadsUpGoingAway) { + mHeadsUpGoingAway = headsUpGoingAway; + if (!headsUpGoingAway) { + waitForStatusBarLayout(); + } + updateTouchableRegionListener(); + } + } + + /** + * Notifies that a remote input textbox in notification gets active or inactive. + * @param entry The entry of the target notification. + * @param remoteInputActive True to notify active, False to notify inactive. + */ + public void setRemoteInputActive( + @NonNull NotificationData.Entry entry, boolean remoteInputActive) { + HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.key); + if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) { + headsUpEntry.remoteInputActive = remoteInputActive; + if (remoteInputActive) { + headsUpEntry.removeAutoRemovalCallbacks(); + } else { + headsUpEntry.updateEntry(false /* updatePostTime */); + } + } + } + + @VisibleForTesting + public void removeMinimumDisplayTimeForTesting() { + mMinimumDisplayTime = 0; + mHeadsUpNotificationDecay = 0; + mTouchAcceptanceDelay = 0; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // HeadsUpManager public methods overrides: + + @Override + public boolean isTrackingHeadsUp() { + return mTrackingHeadsUp; + } + + @Override + public void snooze() { + super.snooze(); + mReleaseOnExpandFinish = true; + } + + /** + * React to the removal of the notification in the heads up. + * + * @return true if the notification was removed and false if it still needs to be kept around + * for a bit since it wasn't shown long enough + */ + @Override + public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) { + if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) { + return super.removeNotification(key, ignoreEarliestRemovalTime); + } else { + HeadsUpEntryPhone entry = getHeadsUpEntryPhone(key); + entry.removeAsSoonAsPossible(); + return false; + } + } + + public void addSwipedOutNotification(@NonNull String key) { + mSwipedOutKeys.add(key); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Dumpable overrides: + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("HeadsUpManagerPhone state:"); + dumpInternal(fd, pw, args); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // ViewTreeObserver.OnComputeInternalInsetsListener overrides: + + /** + * Overridden from TreeObserver. + */ + @Override + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { + if (mIsExpanded || mBar.isBouncerShowing()) { + // The touchable region is always the full area when expanded + return; + } + if (hasPinnedHeadsUp()) { + ExpandableNotificationRow topEntry = getTopEntry().row; + if (topEntry.isChildInGroup()) { + final ExpandableNotificationRow groupSummary + = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification()); + if (groupSummary != null) { + topEntry = groupSummary; + } + } + topEntry.getLocationOnScreen(mTmpTwoArray); + int minX = mTmpTwoArray[0]; + int maxX = mTmpTwoArray[0] + topEntry.getWidth(); + int maxY = topEntry.getIntrinsicHeight(); + + info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + info.touchableRegion.set(minX, 0, maxX, maxY); + } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) { + info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // VisualStabilityManager.Callback overrides: + + @Override + public void onReorderingAllowed() { + mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false); + for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) { + if (isHeadsUp(entry.key)) { + // Maybe the heads-up was removed already + removeHeadsUpEntry(entry); + } + } + mEntriesToRemoveWhenReorderingAllowed.clear(); + mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // HeadsUpManager utility (protected) methods overrides: + + @Override + protected HeadsUpEntry createHeadsUpEntry() { + return mEntryPool.acquire(); + } + + @Override + protected void releaseHeadsUpEntry(HeadsUpEntry entry) { + entry.reset(); + mEntryPool.release((HeadsUpEntryPhone) entry); + } + + @Override + protected boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) { + return mStatusBarState != StatusBarState.KEYGUARD && !mIsExpanded + || super.shouldHeadsUpBecomePinned(entry); + } + + @Override + protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) { + super.dumpInternal(fd, pw, args); + pw.print(" mStatusBarState="); pw.println(mStatusBarState); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Private utility methods: + + @Nullable + private HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) { + return (HeadsUpEntryPhone) getHeadsUpEntry(key); + } + + @Nullable + private HeadsUpEntryPhone getTopHeadsUpEntryPhone() { + return (HeadsUpEntryPhone) getTopHeadsUpEntry(); + } + + private boolean wasShownLongEnough(@NonNull String key) { + if (mSwipedOutKeys.contains(key)) { + // We always instantly dismiss views being manually swiped out. + mSwipedOutKeys.remove(key); + return true; + } + + HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(key); + HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone(); + return headsUpEntry != topEntry || headsUpEntry.wasShownLongEnough(); + } + + /** + * We need to wait on the whole panel to collapse, before we can remove the touchable region + * listener. + */ + private void waitForStatusBarLayout() { + mWaitingOnCollapseWhenGoingAway = true; + mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, + int oldTop, int oldRight, int oldBottom) { + if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) { + mStatusBarWindowView.removeOnLayoutChangeListener(this); + mWaitingOnCollapseWhenGoingAway = false; + updateTouchableRegionListener(); + } + } + }); + } + + private void updateTouchableRegionListener() { + boolean shouldObserve = hasPinnedHeadsUp() || mHeadsUpGoingAway + || mWaitingOnCollapseWhenGoingAway; + if (shouldObserve == mIsObserving) { + return; + } + if (shouldObserve) { + mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this); + mStatusBarWindowView.requestLayout(); + } else { + mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); + } + mIsObserving = shouldObserve; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // HeadsUpEntryPhone: + + protected class HeadsUpEntryPhone extends HeadsUpManager.HeadsUpEntry { + public void setEntry(@NonNull final NotificationData.Entry entry) { + Runnable removeHeadsUpRunnable = () -> { + if (!mVisualStabilityManager.isReorderingAllowed()) { + mEntriesToRemoveWhenReorderingAllowed.add(entry); + mVisualStabilityManager.addReorderingAllowedCallback( + HeadsUpManagerPhone.this); + } else if (!mTrackingHeadsUp) { + removeHeadsUpEntry(entry); + } else { + mEntriesToRemoveAfterExpand.add(entry); + } + }; + + super.setEntry(entry, removeHeadsUpRunnable); + } + + public boolean wasShownLongEnough() { + return earliestRemovaltime < mClock.currentTimeMillis(); + } + + @Override + public void updateEntry(boolean updatePostTime) { + super.updateEntry(updatePostTime); + + if (mEntriesToRemoveAfterExpand.contains(entry)) { + mEntriesToRemoveAfterExpand.remove(entry); + } + if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) { + mEntriesToRemoveWhenReorderingAllowed.remove(entry); + } + } + + @Override + public void expanded(boolean expanded) { + if (this.expanded == expanded) { + return; + } + + this.expanded = expanded; + if (expanded) { + removeAutoRemovalCallbacks(); + } else { + updateEntry(false /* updatePostTime */); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java index c85571c1895d..2bfdefe39017 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -23,7 +23,7 @@ import android.view.ViewConfiguration; import com.android.systemui.Gefingerpoken; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; /** @@ -31,7 +31,7 @@ import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; */ public class HeadsUpTouchHelper implements Gefingerpoken { - private HeadsUpManager mHeadsUpManager; + private HeadsUpManagerPhone mHeadsUpManager; private NotificationStackScrollLayout mStackScroller; private int mTrackingPointer; private float mTouchSlop; @@ -43,7 +43,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken { private NotificationPanelView mPanel; private ExpandableNotificationRow mPickedChild; - public HeadsUpTouchHelper(HeadsUpManager headsUpManager, + public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager, NotificationStackScrollLayout stackScroller, NotificationPanelView notificationPanelView) { mHeadsUpManager = headsUpManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java index df1ffdaf3d87..46d982768179 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java @@ -529,6 +529,13 @@ public class KeyguardAffordanceHelper { KeyguardAffordanceView targetView = left ? mLeftIcon : mRightIcon; KeyguardAffordanceView otherView = left ? mRightIcon : mLeftIcon; startSwiping(targetView); + + // Do not animate the circle expanding if the affordance isn't visible, + // otherwise the circle will be meaningless. + if (targetView.getVisibility() != View.VISIBLE) { + animate = false; + } + if (animate) { fling(0, false, !left); updateIcon(otherView, 0.0f, 0, true, false, true, false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index f0588626e79d..ca66e987933c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -51,6 +51,7 @@ import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; +import android.util.MathUtils; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; @@ -166,6 +167,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private String mLeftButtonStr; private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); private boolean mDozing; + private int mIndicationBottomMargin; + private int mIndicationBottomMarginAmbient; + private float mDarkAmount; + private int mBurnInXOffset; public KeyguardBottomAreaView(Context context) { this(context, null); @@ -235,6 +240,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mEnterpriseDisclosure = findViewById( R.id.keyguard_indication_enterprise_disclosure); mIndicationText = findViewById(R.id.keyguard_indication_text); + mIndicationBottomMargin = getResources().getDimensionPixelSize( + R.dimen.keyguard_indication_margin_bottom); + mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize( + R.dimen.keyguard_indication_margin_bottom_ambient); updateCameraVisibility(); mUnlockMethodCache = UnlockMethodCache.getInstance(getContext()); mUnlockMethodCache.addListener(this); @@ -303,11 +312,13 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - int indicationBottomMargin = getResources().getDimensionPixelSize( + mIndicationBottomMargin = getResources().getDimensionPixelSize( R.dimen.keyguard_indication_margin_bottom); + mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize( + R.dimen.keyguard_indication_margin_bottom_ambient); MarginLayoutParams mlp = (MarginLayoutParams) mIndicationArea.getLayoutParams(); - if (mlp.bottomMargin != indicationBottomMargin) { - mlp.bottomMargin = indicationBottomMargin; + if (mlp.bottomMargin != mIndicationBottomMargin) { + mlp.bottomMargin = mIndicationBottomMargin; mIndicationArea.setLayoutParams(mlp); } @@ -543,6 +554,22 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } } + public void setDarkAmount(float darkAmount) { + if (darkAmount == mDarkAmount) { + return; + } + mDarkAmount = darkAmount; + // Let's randomize the bottom margin every time we wake up to avoid burn-in. + if (darkAmount == 0) { + mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize( + R.dimen.keyguard_indication_margin_bottom_ambient) + + (int) (Math.random() * mIndicationText.getTextSize()); + } + mIndicationArea.setAlpha(MathUtils.lerp(1f, 0.7f, darkAmount)); + mIndicationArea.setTranslationY(MathUtils.lerp(0, + mIndicationBottomMargin - mIndicationBottomMarginAmbient, darkAmount)); + } + private static boolean isSuccessfulLaunch(int result) { return result == ActivityManager.START_SUCCESS || result == ActivityManager.START_DELIVERED_TO_TOP @@ -687,11 +714,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL if (mRightAffordanceView.getVisibility() == View.VISIBLE) { startFinishDozeAnimationElement(mRightAffordanceView, delay); } - mIndicationArea.setAlpha(0f); - mIndicationArea.animate() - .alpha(1f) - .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN) - .setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION); } private void startFinishDozeAnimationElement(View element, long delay) { @@ -815,6 +837,22 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } } + public void dozeTimeTick() { + if (mDarkAmount == 1) { + // Move indication every minute to avoid burn-in + final int dozeTranslation = mIndicationBottomMargin - mIndicationBottomMarginAmbient; + mIndicationArea.setTranslationY(dozeTranslation + (float) Math.random() * 5); + } + } + + public void setBurnInXOffset(int burnInXOffset) { + if (mBurnInXOffset == burnInXOffset) { + return; + } + mBurnInXOffset = burnInXOffset; + mIndicationArea.setTranslationX(burnInXOffset); + } + private class DefaultLeftButton implements IntentButton { private IconState mIconState = new IconState(); @@ -822,8 +860,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @Override public IconState getIcon() { mLeftIsVoiceAssist = canLaunchVoiceAssist(); + final boolean showAffordance = + getResources().getBoolean(R.bool.config_keyguardShowLeftAffordance); if (mLeftIsVoiceAssist) { - mIconState.isVisible = mUserSetupComplete; + mIconState.isVisible = mUserSetupComplete && showAffordance; if (mLeftAssistIcon == null) { mIconState.drawable = mContext.getDrawable(R.drawable.ic_mic_26dp); } else { @@ -832,7 +872,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mIconState.contentDescription = mContext.getString( R.string.accessibility_voice_assist_button); } else { - mIconState.isVisible = mUserSetupComplete && isPhoneVisible(); + mIconState.isVisible = mUserSetupComplete && showAffordance && isPhoneVisible(); mIconState.drawable = mContext.getDrawable(R.drawable.ic_phone_24dp); mIconState.contentDescription = mContext.getString( R.string.accessibility_phone_button); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 99debee2dad3..699e8cf145bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone; -import android.app.ActivityManager; import android.content.Context; import android.os.Handler; import android.os.UserHandle; @@ -102,7 +101,7 @@ public class KeyguardBouncer { return; } - final int activeUserId = ActivityManager.getCurrentUser(); + final int activeUserId = KeyguardUpdateMonitor.getCurrentUser(); final boolean isSystemUser = UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM; final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId; @@ -122,6 +121,8 @@ public class KeyguardBouncer { // Split up the work over multiple frames. DejankUtils.postAfterTraversal(mShowRunnable); + + mCallback.onBouncerVisiblityChanged(true /* shown */); } private final Runnable mShowRunnable = new Runnable() { @@ -130,6 +131,10 @@ public class KeyguardBouncer { mRoot.setVisibility(View.VISIBLE); mKeyguardView.onResume(); showPromptReason(mBouncerPromptReason); + final CharSequence customMessage = mCallback.consumeCustomMessage(); + if (customMessage != null) { + mKeyguardView.showErrorMessage(customMessage); + } // We might still be collapsed and the view didn't have time to layout yet or still // be small, let's wait on the predraw to do the animation in that case. if (mKeyguardView.getHeight() != 0 && mKeyguardView.getHeight() != mStatusBarHeight) { @@ -182,6 +187,7 @@ public class KeyguardBouncer { mDismissCallbackRegistry.notifyDismissCancelled(); } mFalsingManager.onBouncerHidden(); + mCallback.onBouncerVisiblityChanged(false /* shown */); cancelShowRunnable(); if (mKeyguardView != null) { mKeyguardView.cancelDismissAction(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index f7aa818f0abf..389be1ad6d77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -20,6 +20,7 @@ import static com.android.systemui.statusbar.notification.NotificationUtils.inte import android.content.res.Resources; import android.graphics.Path; +import android.util.MathUtils; import android.view.animation.AccelerateInterpolator; import android.view.animation.PathInterpolator; @@ -45,8 +46,7 @@ public class KeyguardClockPositionAlgorithm { private static final float BURN_IN_PREVENTION_PERIOD_Y = 521; private static final float BURN_IN_PREVENTION_PERIOD_X = 83; - private int mClockNotificationsMarginMin; - private int mClockNotificationsMarginMax; + private int mClockNotificationsMargin; private float mClockYFractionMin; private float mClockYFractionMax; private int mMaxKeyguardNotifications; @@ -84,10 +84,8 @@ public class KeyguardClockPositionAlgorithm { * Refreshes the dimension values. */ public void loadDimens(Resources res) { - mClockNotificationsMarginMin = res.getDimensionPixelSize( - R.dimen.keyguard_clock_notifications_margin_min); - mClockNotificationsMarginMax = res.getDimensionPixelSize( - R.dimen.keyguard_clock_notifications_margin_max); + mClockNotificationsMargin = res.getDimensionPixelSize( + R.dimen.keyguard_clock_notifications_margin); mClockYFractionMin = res.getFraction(R.fraction.keyguard_clock_y_fraction_min, 1, 1); mClockYFractionMax = res.getFraction(R.fraction.keyguard_clock_y_fraction_max, 1, 1); mMoreCardNotificationAmount = @@ -117,7 +115,7 @@ public class KeyguardClockPositionAlgorithm { public float getMinStackScrollerPadding(int height, int keyguardStatusHeight) { return mClockYFractionMin * height + keyguardStatusHeight / 2 - + mClockNotificationsMarginMin; + + mClockNotificationsMargin; } public void run(Result result) { @@ -125,21 +123,15 @@ public class KeyguardClockPositionAlgorithm { float clockAdjustment = getClockYExpansionAdjustment(); float topPaddingAdjMultiplier = getTopPaddingAdjMultiplier(); result.stackScrollerPaddingAdjustment = (int) (clockAdjustment*topPaddingAdjMultiplier); - int clockNotificationsPadding = getClockNotificationsPadding() + result.clockY = y; + int clockNotificationsPadding = mClockNotificationsMargin + result.stackScrollerPaddingAdjustment; int padding = y + clockNotificationsPadding; - result.clockY = y; - result.stackScrollerPadding = mKeyguardStatusHeight + padding; - result.clockScale = getClockScale(result.stackScrollerPadding, - result.clockY, - y + getClockNotificationsPadding() + mKeyguardStatusHeight); + result.clockScale = getClockScale(mKeyguardStatusHeight + padding, + y, y + mClockNotificationsMargin + mKeyguardStatusHeight); result.clockAlpha = getClockAlpha(result.clockScale); - result.stackScrollerPadding = (int) interpolate( - result.stackScrollerPadding, - mClockBottom + y + mDozingStackPadding, - mDarkAmount); - + result.stackScrollerPadding = mClockBottom + y + mDozingStackPadding; result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount); } @@ -154,22 +146,16 @@ public class KeyguardClockPositionAlgorithm { return interpolate(progress, 1, mDarkAmount); } - private int getClockNotificationsPadding() { - float t = getNotificationAmountT(); - t = Math.min(t, 1.0f); - return (int) (t * mClockNotificationsMarginMin + (1 - t) * mClockNotificationsMarginMax); - } - private float getClockYFraction() { float t = getNotificationAmountT(); t = Math.min(t, 1.0f); - return (1 - t) * mClockYFractionMax + t * mClockYFractionMin; + return MathUtils.lerp(mClockYFractionMax, mClockYFractionMin, t); } private int getClockY() { - // Dark: Align the bottom edge of the clock at one third: - // clockBottomEdge = result - mKeyguardStatusHeight / 2 + mClockBottom - float clockYDark = (0.33f * mHeight + (float) mKeyguardStatusHeight / 2 - mClockBottom) + // Dark: Align the bottom edge of the clock at about half of the screen: + float clockYDark = (mClockYFractionMax * mHeight + + (float) mKeyguardStatusHeight / 2 - mClockBottom) + burnInPreventionOffsetY(); float clockYRegular = getClockYFraction() * mHeight; return (int) interpolate(clockYRegular, clockYDark, mDarkAmount); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index a6691b1656df..09acf3e4b53b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -115,7 +115,7 @@ public class KeyguardStatusBarView extends RelativeLayout lp = (MarginLayoutParams) mSystemIconsSuperContainer.getLayoutParams(); lp.height = getResources().getDimensionPixelSize( - R.dimen.status_bar_header_height); + com.android.internal.R.dimen.quick_qs_total_height); lp.setMarginStart(getResources().getDimensionPixelSize( R.dimen.system_icons_super_container_margin_start)); mSystemIconsSuperContainer.setLayoutParams(lp); @@ -333,7 +333,7 @@ public class KeyguardStatusBarView extends RelativeLayout return false; } - public void onOverlayChanged() { + public void onThemeChanged() { @ColorInt int textColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor); @ColorInt int iconColor = Utils.getDefaultColor(mContext, Color.luminance(textColor) < 0.5 ? R.color.dark_mode_icon_color_single_tone : diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index 533771a31d0e..1c361caa4a1a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -175,9 +175,8 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private boolean isLight(int vis, int barMode, int flag) { boolean isTransparentBar = (barMode == MODE_TRANSPARENT || barMode == MODE_LIGHTS_OUT_TRANSPARENT); - boolean allowLight = isTransparentBar && !mBatteryController.isPowerSave(); boolean light = (vis & flag) != 0; - return allowLight && light; + return isTransparentBar && light; } private boolean animateChange() { @@ -264,8 +263,10 @@ public class LightBarController implements BatteryController.BatteryStateChangeC pw.println(" StatusBarTransitionsController:"); mStatusBarIconController.getTransitionsController().dump(fd, pw, args); pw.println(); - pw.println(" NavigationBarTransitionsController:"); - mNavigationBarController.dump(fd, pw, args); - pw.println(); + if (mNavigationBarController != null) { + pw.println(" NavigationBarTransitionsController:"); + mNavigationBarController.dump(fd, pw, args); + pw.println(); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java index 5c9446ce8672..264f5749fa73 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java @@ -250,7 +250,7 @@ public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChange } break; case STATE_FACE_UNLOCK: - iconRes = com.android.internal.R.drawable.ic_account_circle; + iconRes = R.drawable.ic_face_unlock; break; case STATE_FINGERPRINT: // If screen is off and device asleep, use the draw on animation so the first frame diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java index 87f5ca7adf73..40ddf5b497ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java @@ -124,8 +124,8 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen } else { if (selectedUser != null) { // Show the selected user's static wallpaper. - return LoaderResult.success( - mWallpaperManager.getBitmapAsUser(selectedUser.getIdentifier())); + return LoaderResult.success(mWallpaperManager.getBitmapAsUser( + selectedUser.getIdentifier(), true /* hardware */)); } else { // When there is no selected user, show the system wallpaper diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java index 316bd5bcaca2..0f8d59b12ddb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java @@ -61,14 +61,10 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { public void setWorkModeEnabled(boolean enableWorkMode) { synchronized (mProfiles) { for (UserInfo ui : mProfiles) { - if (enableWorkMode) { - if (!mUserManager.trySetQuietModeDisabled(ui.id, null)) { - StatusBarManager statusBarManager = (StatusBarManager) mContext - .getSystemService(android.app.Service.STATUS_BAR_SERVICE); - statusBarManager.collapsePanels(); - } - } else { - mUserManager.setQuietModeEnabled(ui.id, true); + if (!mUserManager.requestQuietModeEnabled(!enableWorkMode, UserHandle.of(ui.id))) { + StatusBarManager statusBarManager = (StatusBarManager) mContext + .getSystemService(android.app.Service.STATUS_BAR_SERVICE); + statusBarManager.collapsePanels(); } } } 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 c44e1db367b8..72938c25b753 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -22,8 +22,13 @@ import static android.app.StatusBarManager.windowStateToString; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_WINDOW_STATE; import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions; +import static com.android.systemui.OverviewProxyService.OverviewProxyListener; import android.accessibilityservice.AccessibilityServiceInfo; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.annotation.IdRes; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerNative; @@ -39,6 +44,7 @@ import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.drawable.AnimatedVectorDrawable; import android.inputmethodservice.InputMethodService; import android.os.Binder; import android.os.Bundle; @@ -56,6 +62,7 @@ import android.view.IRotationWatcher.Stub; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -64,22 +71,29 @@ import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; +import android.widget.Button; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.keyguard.LatencyTracker; +import com.android.internal.util.LatencyTracker; import com.android.systemui.Dependency; +import com.android.systemui.OverviewProxyService; +import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.SysUiServiceProvider; import com.android.systemui.assist.AssistManager; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.recents.Recents; +import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; +import com.android.systemui.statusbar.policy.KeyButtonDrawable; import com.android.systemui.statusbar.policy.KeyButtonView; +import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.stack.StackStateAnimator; import java.io.FileDescriptor; @@ -107,9 +121,11 @@ public class NavigationBarFragment extends Fragment implements Callbacks { private int mNavigationIconHints = 0; private int mNavigationBarMode; + private boolean mAccessibilityFeedbackEnabled; private AccessibilityManager mAccessibilityManager; private MagnificationContentObserver mMagnificationObserver; private ContentResolver mContentResolver; + private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); private int mDisabledFlags1; private StatusBar mStatusBar; @@ -125,8 +141,32 @@ public class NavigationBarFragment extends Fragment implements Callbacks { private int mSystemUiVisibility; private LightBarController mLightBarController; + private OverviewProxyService mOverviewProxyService; + public boolean mHomeBlockedThisTouch; + private int mLastRotationSuggestion; + private boolean mHoveringRotationSuggestion; + private RotationLockController mRotationLockController; + private TaskStackListenerImpl mTaskStackListener; + + private final Runnable mRemoveRotationProposal = () -> safeSetRotationButtonState(false); + private Animator mRotateShowAnimator; + private Animator mRotateHideAnimator; + + private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() { + @Override + public void onConnectionChanged(boolean isConnected) { + mNavigationBarView.onOverviewProxyConnectionChanged(isConnected); + updateScreenPinningGestures(); + } + + @Override + public void onRecentsAnimationStarted() { + mNavigationBarView.setRecentsAnimationStarted(true); + } + }; + // ----- Fragment Lifecycle Callbacks ----- @Override @@ -152,6 +192,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0); } mAssistManager = Dependency.get(AssistManager.class); + mOverviewProxyService = Dependency.get(OverviewProxyService.class); try { WindowManagerGlobal.getWindowManagerService() @@ -159,6 +200,12 @@ public class NavigationBarFragment extends Fragment implements Callbacks { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + + mRotationLockController = Dependency.get(RotationLockController.class); + + // Register the task stack listener + mTaskStackListener = new TaskStackListenerImpl(); + ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); } @Override @@ -174,6 +221,9 @@ public class NavigationBarFragment extends Fragment implements Callbacks { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + + // Unregister the task stack listener + ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener); } @Override @@ -188,7 +238,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { mNavigationBarView = (NavigationBarView) view; mNavigationBarView.setDisabledFlags(mDisabledFlags1); - mNavigationBarView.setComponents(mRecents, mDivider); + mNavigationBarView.setComponents(mRecents, mDivider, mStatusBar.getPanel()); mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged); mNavigationBarView.setOnTouchListener(this::onNavigationTouch); if (savedInstanceState != null) { @@ -202,12 +252,14 @@ public class NavigationBarFragment extends Fragment implements Callbacks { filter.addAction(Intent.ACTION_SCREEN_ON); getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); notifyNavigationBarScreenOn(); + mOverviewProxyService.addCallback(mOverviewProxyListener); } @Override public void onDestroyView() { super.onDestroyView(); mNavigationBarView.getLightTransitionsController().destroy(getContext()); + mOverviewProxyService.removeCallback(mOverviewProxyListener); getContext().unregisterReceiver(mBroadcastReceiver); } @@ -263,7 +315,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { boolean showImeSwitcher) { boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0; int hints = mNavigationIconHints; - if ((backDisposition == InputMethodService.BACK_DISPOSITION_WILL_DISMISS) || imeShown) { + if (imeShown && backDisposition != InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS) { hints |= NAVIGATION_HINT_BACK_ALT; } else { hints &= ~NAVIGATION_HINT_BACK_ALT; @@ -300,6 +352,64 @@ public class NavigationBarFragment extends Fragment implements Callbacks { } } + @Override + public void onRotationProposal(final int rotation, boolean isValid) { + // This method will be called on rotation suggestion changes even if the proposed rotation + // is not valid for the top app. Use invalid rotation choices as a signal to remove the + // rotate button if shown. + + if (!isValid) { + safeSetRotationButtonState(false); + return; + } + + if (rotation == mWindowManager.getDefaultDisplay().getRotation()) { + // Use this as a signal to remove any current suggestions + getView().getHandler().removeCallbacks(mRemoveRotationProposal); + safeSetRotationButtonState(false); + } else { + mLastRotationSuggestion = rotation; // Remember rotation for click + safeSetRotationButtonState(true); + rescheduleRotationTimeout(false); + mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN); + } + } + + private void safeSetRotationButtonState(boolean vis) { + if (mNavigationBarView != null) mNavigationBarView.setRotateSuggestionButtonState(vis); + } + + private void safeSetRotationButtonState(boolean vis, boolean force) { + if (mNavigationBarView != null) { + mNavigationBarView.setRotateSuggestionButtonState(vis, force); + } + } + + private void rescheduleRotationTimeout(final boolean reasonHover) { + // May be called due to a new rotation proposal or a change in hover state + if (reasonHover) { + // Don't reschedule if a hide animator is running + if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) { + return; + } + // Don't reschedule if not visible + if (mNavigationBarView.getRotateSuggestionButton().getVisibility() != View.VISIBLE) { + return; + } + } + + Handler h = getView().getHandler(); + h.removeCallbacks(mRemoveRotationProposal); // Stop any pending removal + h.postDelayed(mRemoveRotationProposal, + computeRotationProposalTimeout()); // Schedule timeout + } + + private int computeRotationProposalTimeout() { + if (mAccessibilityFeedbackEnabled) return 20000; + if (mHoveringRotationSuggestion) return 16000; + return 6000; + } + // Injected from StatusBar at creation. public void setCurrentSysuiVisibility(int systemUiVisibility) { mSystemUiVisibility = systemUiVisibility; @@ -351,6 +461,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { if (masked != mDisabledFlags1) { mDisabledFlags1 = masked; if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state1); + updateScreenPinningGestures(); } } @@ -364,7 +475,8 @@ public class NavigationBarFragment extends Fragment implements Callbacks { private boolean shouldDisableNavbarGestures() { return !mStatusBar.isDeviceProvisioned() - || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0; + || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0 + || mNavigationBarView.getRecentsButton().getVisibility() != View.VISIBLE; } private void repositionNavigationBar() { @@ -376,6 +488,24 @@ public class NavigationBarFragment extends Fragment implements Callbacks { ((View) mNavigationBarView.getParent()).getLayoutParams()); } + private void updateScreenPinningGestures() { + if (mNavigationBarView == null) { + return; + } + + // Change the cancel pin gesture to home and back if recents button is invisible + boolean recentsVisible = mNavigationBarView.isRecentsButtonVisible(); + ButtonDispatcher homeButton = mNavigationBarView.getHomeButton(); + ButtonDispatcher backButton = mNavigationBarView.getBackButton(); + if (recentsVisible) { + homeButton.setOnLongClickListener(this::onHomeLongClick); + backButton.setOnLongClickListener(this::onLongPressBackRecents); + } else { + homeButton.setOnLongClickListener(this::onLongPressBackHome); + backButton.setOnLongClickListener(this::onLongPressBackHome); + } + } + private void notifyNavigationBarScreenOn() { mNavigationBarView.notifyScreenOn(); } @@ -391,16 +521,19 @@ public class NavigationBarFragment extends Fragment implements Callbacks { ButtonDispatcher backButton = mNavigationBarView.getBackButton(); backButton.setLongClickable(true); - backButton.setOnLongClickListener(this::onLongPressBackRecents); ButtonDispatcher homeButton = mNavigationBarView.getHomeButton(); homeButton.setOnTouchListener(this::onHomeTouch); - homeButton.setOnLongClickListener(this::onHomeLongClick); ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton(); accessibilityButton.setOnClickListener(this::onAccessibilityClick); accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick); updateAccessibilityServicesState(mAccessibilityManager); + + ButtonDispatcher rotateSuggestionButton = mNavigationBarView.getRotateSuggestionButton(); + rotateSuggestionButton.setOnClickListener(this::onRotateSuggestionClick); + rotateSuggestionButton.setOnHoverListener(this::onRotateSuggestionHover); + updateScreenPinningGestures(); } private boolean onHomeTouch(View v, MotionEvent event) { @@ -437,7 +570,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { } private boolean onNavigationTouch(View v, MotionEvent event) { - mStatusBar.checkUserAutohide(v, event); + mStatusBar.checkUserAutohide(event); return false; } @@ -446,9 +579,10 @@ public class NavigationBarFragment extends Fragment implements Callbacks { if (shouldDisableNavbarGestures()) { return false; } - MetricsLogger.action(getContext(), MetricsEvent.ACTION_ASSIST_LONG_PRESS); + mMetricsLogger.action(MetricsEvent.ACTION_ASSIST_LONG_PRESS); mAssistManager.startAssist(new Bundle() /* args */); mStatusBar.awakenDreams(); + if (mNavigationBarView != null) { mNavigationBarView.abortCurrentGesture(); } @@ -480,20 +614,29 @@ public class NavigationBarFragment extends Fragment implements Callbacks { mCommandQueue.toggleRecentApps(); } + private boolean onLongPressBackHome(View v) { + return onLongPressNavigationButtons(v, R.id.back, R.id.home); + } + + private boolean onLongPressBackRecents(View v) { + return onLongPressNavigationButtons(v, R.id.back, R.id.recent_apps); + } + /** - * This handles long-press of both back and recents. They are - * handled together to capture them both being long-pressed + * This handles long-press of both back and recents/home. Back is the common button with + * combination of recents if it is visible or home if recents is invisible. + * They are handled together to capture them both being long-pressed * at the same time to exit screen pinning (lock task). * - * When accessibility mode is on, only a long-press from recents + * When accessibility mode is on, only a long-press from recents/home * is required to exit. * * In all other circumstances we try to pass through long-press events * for Back, so that apps can still use it. Which can be from two things. * 1) Not currently in screen pinning (lock task). - * 2) Back is long-pressed without recents. + * 2) Back is long-pressed without recents/home. */ - private boolean onLongPressBackRecents(View v) { + private boolean onLongPressNavigationButtons(View v, @IdRes int btnId1, @IdRes int btnId2) { try { boolean sendBackLongPress = false; IActivityManager activityManager = ActivityManagerNative.getDefault(); @@ -501,33 +644,40 @@ public class NavigationBarFragment extends Fragment implements Callbacks { boolean inLockTaskMode = activityManager.isInLockTaskMode(); if (inLockTaskMode && !touchExplorationEnabled) { long time = System.currentTimeMillis(); + // If we recently long-pressed the other button then they were // long-pressed 'together' if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) { - activityManager.stopLockTaskMode(); + activityManager.stopSystemLockTaskMode(); // When exiting refresh disabled flags. mNavigationBarView.setDisabledFlags(mDisabledFlags1, true); return true; - } else if ((v.getId() == R.id.back) - && !mNavigationBarView.getRecentsButton().getCurrentView().isPressed()) { - // If we aren't pressing recents right now then they presses - // won't be together, so send the standard long-press action. - sendBackLongPress = true; + } else if (v.getId() == btnId1) { + ButtonDispatcher button = btnId2 == R.id.recent_apps + ? mNavigationBarView.getRecentsButton() + : mNavigationBarView.getHomeButton(); + if (!button.getCurrentView().isPressed()) { + // If we aren't pressing recents/home right now then they presses + // won't be together, so send the standard long-press action. + sendBackLongPress = true; + } } mLastLockToAppLongPress = time; } else { // If this is back still need to handle sending the long-press event. - if (v.getId() == R.id.back) { + if (v.getId() == btnId1) { sendBackLongPress = true; } else if (touchExplorationEnabled && inLockTaskMode) { - // When in accessibility mode a long press that is recents (not back) + // When in accessibility mode a long press that is recents/home (not back) // should stop lock task. - activityManager.stopLockTaskMode(); + activityManager.stopSystemLockTaskMode(); // When exiting refresh disabled flags. mNavigationBarView.setDisabledFlags(mDisabledFlags1, true); return true; - } else if (v.getId() == R.id.recent_apps) { - return onLongPressRecents(); + } else if (v.getId() == btnId2) { + return btnId2 == R.id.recent_apps + ? onLongPressRecents() + : onHomeLongClick(mNavigationBarView.getHomeButton().getCurrentView()); } } if (sendBackLongPress) { @@ -575,6 +725,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { } catch (Settings.SettingNotFoundException e) { } + boolean feedbackEnabled = false; // AccessibilityManagerService resolves services for the current user since the local // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission final List<AccessibilityServiceInfo> services = @@ -585,13 +736,33 @@ public class NavigationBarFragment extends Fragment implements Callbacks { if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) { requestingServices++; } + + if (info.feedbackType != 0 && info.feedbackType != + AccessibilityServiceInfo.FEEDBACK_GENERIC) { + feedbackEnabled = true; + } } + mAccessibilityFeedbackEnabled = feedbackEnabled; + final boolean showAccessibilityButton = requestingServices >= 1; final boolean targetSelection = requestingServices >= 2; mNavigationBarView.setAccessibilityButtonState(showAccessibilityButton, targetSelection); } + private void onRotateSuggestionClick(View v) { + mMetricsLogger.action(MetricsEvent.ACTION_ROTATION_SUGGESTION_ACCEPTED); + mRotationLockController.setRotationLockedAtAngle(true, mLastRotationSuggestion); + } + + private boolean onRotateSuggestionHover(View v, MotionEvent event) { + final int action = event.getActionMasked(); + mHoveringRotationSuggestion = (action == MotionEvent.ACTION_HOVER_ENTER) + || (action == MotionEvent.ACTION_HOVER_MOVE); + rescheduleRotationTimeout(true); + return false; // Must return false so a11y hover events are dispatched correctly. + } + // ----- Methods that StatusBar talks to (should be minimized) ----- public void setLightBarController(LightBarController lightBarController) { @@ -639,11 +810,21 @@ public class NavigationBarFragment extends Fragment implements Callbacks { private final Stub mRotationWatcher = new Stub() { @Override - public void onRotationChanged(int rotation) throws RemoteException { + public void onRotationChanged(final int rotation) throws RemoteException { // We need this to be scheduled as early as possible to beat the redrawing of // window in response to the orientation change. Handler h = getView().getHandler(); Message msg = Message.obtain(h, () -> { + + // If the screen rotation changes while locked, potentially update lock to flow with + // new screen rotation and hide any showing suggestions. + if (mRotationLockController.isRotationLocked()) { + if (shouldOverrideUserLockPrefs(rotation)) { + mRotationLockController.setRotationLockedAtAngle(true, rotation); + } + safeSetRotationButtonState(false, true); + } + if (mNavigationBarView != null && mNavigationBarView.needsReorient(rotation)) { repositionNavigationBar(); @@ -652,6 +833,12 @@ public class NavigationBarFragment extends Fragment implements Callbacks { msg.setAsynchronous(true); h.sendMessageAtFrontOfQueue(msg); } + + private boolean shouldOverrideUserLockPrefs(final int rotation) { + // Only override user prefs when returning to portrait. + // Don't let apps that force landscape or 180 alter user lock. + return rotation == Surface.ROTATION_0; + } }; private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -665,6 +852,31 @@ public class NavigationBarFragment extends Fragment implements Callbacks { } }; + class TaskStackListenerImpl extends SysUiTaskStackChangeListener { + // Invalidate any rotation suggestion on task change or activity orientation change + // Note: all callbacks happen on main thread + + @Override + public void onTaskStackChanged() { + safeSetRotationButtonState(false); + } + + @Override + public void onTaskRemoved(int taskId) { + safeSetRotationButtonState(false); + } + + @Override + public void onTaskMovedToFront(int taskId) { + safeSetRotationButtonState(false); + } + + @Override + public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { + safeSetRotationButtonState(false); + } + } + public static View create(Context context, FragmentListener listener) { WindowManager.LayoutParams lp = new WindowManager.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, @@ -678,6 +890,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { PixelFormat.TRANSLUCENT); lp.token = new Binder(); lp.setTitle("NavigationBar"); + lp.accessibilityTitle = context.getString(R.string.nav_bar); lp.windowAnimations = 0; View navigationBarView = LayoutInflater.from(context).inflate( 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 ee9a791585c4..d15c771ec098 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java @@ -16,36 +16,42 @@ package com.android.systemui.statusbar.phone; +import static android.view.WindowManager.DOCKED_INVALID; +import static android.view.WindowManager.DOCKED_LEFT; +import static android.view.WindowManager.DOCKED_TOP; +import static com.android.systemui.OverviewProxyService.DEBUG_OVERVIEW_PROXY; +import static com.android.systemui.OverviewProxyService.TAG_OPS; + import android.app.ActivityManager; import android.content.Context; import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Matrix; import android.graphics.Rect; -import android.view.GestureDetector; +import android.os.RemoteException; +import android.util.Log; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; -import android.view.ViewConfiguration; - -import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; import com.android.systemui.Dependency; +import com.android.systemui.OverviewProxyService; +import com.android.systemui.OverviewProxyService.OverviewProxyListener; import com.android.systemui.R; 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.stackdivider.Divider; import com.android.systemui.tuner.TunerService; -import static android.view.WindowManager.DOCKED_INVALID; -import static android.view.WindowManager.DOCKED_LEFT; -import static android.view.WindowManager.DOCKED_TOP; - /** * Class to detect gestures on the navigation bar. */ -public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureListener - implements TunerService.Tunable, GestureHelper { +public class NavigationBarGestureHelper implements TunerService.Tunable, GestureHelper { + private static final String TAG = "NavBarGestureHelper"; private static final String KEY_DOCK_WINDOW_GESTURE = "overview_nav_bar_gesture"; /** * When dragging from the navigation bar, we drag in recents. @@ -67,32 +73,43 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL private Context mContext; private NavigationBarView mNavigationBarView; private boolean mIsVertical; - private boolean mIsRTL; - private final GestureDetector mTaskSwitcherDetector; + private final QuickScrubController mQuickScrubController; private final int mScrollTouchSlop; - private final int mMinFlingVelocity; + private final Matrix mTransformGlobalMatrix = new Matrix(); + private final Matrix mTransformLocalMatrix = new Matrix(); + private final StatusBar mStatusBar; private int mTouchDownX; private int mTouchDownY; private boolean mDownOnRecents; private VelocityTracker mVelocityTracker; + private OverviewProxyService mOverviewProxyService = Dependency.get(OverviewProxyService.class); + private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() { + @Override + public void onRecentsAnimationStarted() { + mRecentsAnimationStarted = true; + mQuickScrubController.cancelQuickSwitch(); + } + }; + private boolean mRecentsAnimationStarted; private boolean mDockWindowEnabled; private boolean mDockWindowTouchSlopExceeded; private int mDragMode; public NavigationBarGestureHelper(Context context) { mContext = context; - ViewConfiguration configuration = ViewConfiguration.get(context); + mStatusBar = SysUiServiceProvider.getComponent(context, StatusBar.class); Resources r = context.getResources(); mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance); - mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity(); - mTaskSwitcherDetector = new GestureDetector(context, this); + mQuickScrubController = new QuickScrubController(context); Dependency.get(TunerService.class).addTunable(this, KEY_DOCK_WINDOW_GESTURE); + mOverviewProxyService.addCallback(mOverviewProxyListener); } public void destroy() { Dependency.get(TunerService.class).removeTunable(this); + mOverviewProxyService.removeCallback(mOverviewProxyListener); } public void setComponents(RecentsComponent recentsComponent, Divider divider, @@ -100,42 +117,89 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL mRecentsComponent = recentsComponent; mDivider = divider; mNavigationBarView = navigationBarView; + mQuickScrubController.setComponents(mNavigationBarView); } public void setBarState(boolean isVertical, boolean isRTL) { mIsVertical = isVertical; - mIsRTL = isRTL; + mQuickScrubController.setBarState(isVertical, isRTL); + } + + private boolean proxyMotionEvents(MotionEvent event) { + final IOverviewProxy overviewProxy = mOverviewProxyService.getProxy(); + if (overviewProxy != null) { + mNavigationBarView.requestUnbufferedDispatch(event); + event.transform(mTransformGlobalMatrix); + try { + overviewProxy.onMotionEvent(event); + if (DEBUG_OVERVIEW_PROXY) { + Log.d(TAG_OPS, "Send MotionEvent: " + event.toString()); + } + return true; + } catch (RemoteException e) { + Log.e(TAG, "Callback failed", e); + } finally { + event.transform(mTransformLocalMatrix); + } + } + return false; } public boolean onInterceptTouchEvent(MotionEvent event) { - // If we move more than a fixed amount, then start capturing for the - // task switcher detector - mTaskSwitcherDetector.onTouchEvent(event); int action = event.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { mTouchDownX = (int) event.getX(); mTouchDownY = (int) event.getY(); + mTransformGlobalMatrix.set(Matrix.IDENTITY_MATRIX); + mTransformLocalMatrix.set(Matrix.IDENTITY_MATRIX); + mNavigationBarView.transformMatrixToGlobal(mTransformGlobalMatrix); + mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix); + mRecentsAnimationStarted = false; break; } - case MotionEvent.ACTION_MOVE: { - int x = (int) event.getX(); - int y = (int) event.getY(); - int xDiff = Math.abs(x - mTouchDownX); - int yDiff = Math.abs(y - mTouchDownY); - boolean exceededTouchSlop = !mIsVertical - ? xDiff > mScrollTouchSlop && xDiff > yDiff - : yDiff > mScrollTouchSlop && yDiff > xDiff; - if (exceededTouchSlop) { - return true; - } - break; - } - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - break; } - return mDockWindowEnabled && interceptDockWindowEvent(event); + boolean handledByQuickscrub = mQuickScrubController.onInterceptTouchEvent(event); + if (mStatusBar.isPresenterFullyCollapsed() && !handledByQuickscrub) { + // Proxy motion events until we start intercepting for quickscrub + proxyMotionEvents(event); + } + + boolean result = handledByQuickscrub; + result |= mRecentsAnimationStarted; + if (mDockWindowEnabled) { + result |= interceptDockWindowEvent(event); + } + return result; + } + + public boolean onTouchEvent(MotionEvent event) { + // The same down event was just sent on intercept and therefore can be ignored here + boolean ignoreProxyDownEvent = event.getAction() == MotionEvent.ACTION_DOWN + && mOverviewProxyService.getProxy() != null; + boolean result = mStatusBar.isPresenterFullyCollapsed() + && (mQuickScrubController.onTouchEvent(event) + || ignoreProxyDownEvent + || proxyMotionEvents(event)); + result |= mRecentsAnimationStarted; + if (mDockWindowEnabled) { + result |= handleDockWindowEvent(event); + } + return result; + } + + public void onDraw(Canvas canvas) { + if (mOverviewProxyService.getProxy() != null) { + mQuickScrubController.onDraw(canvas); + } + } + + public void onLayout(boolean changed, int left, int top, int right, int bottom) { + mQuickScrubController.onLayout(changed, left, top, right, bottom); + } + + public void onDarkIntensityChange(float intensity) { + mQuickScrubController.onDarkIntensityChange(intensity); } private boolean interceptDockWindowEvent(MotionEvent event) { @@ -206,7 +270,7 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL && mDivider.getView().getWindowManagerProxy().getDockSide() == DOCKED_INVALID) { Rect initialBounds = null; int dragMode = calculateDragMode(); - int createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; + int createMode = ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; if (dragMode == DRAG_MODE_DIVIDER) { initialBounds = new Rect(); mDivider.getView().calculateBoundsForPosition(mIsVertical @@ -218,10 +282,10 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL initialBounds); } else if (dragMode == DRAG_MODE_RECENTS && mTouchDownX < mContext.getResources().getDisplayMetrics().widthPixels / 2) { - createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; + createMode = ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT; } - boolean docked = mRecentsComponent.dockTopTask(dragMode, createMode, initialBounds, - MetricsEvent.ACTION_WINDOW_DOCK_SWIPE); + boolean docked = mRecentsComponent.splitPrimaryTask(dragMode, createMode, + initialBounds, MetricsEvent.ACTION_WINDOW_DOCK_SWIPE); if (docked) { mDragMode = dragMode; if (mDragMode == DRAG_MODE_DIVIDER) { @@ -274,37 +338,6 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL return DRAG_MODE_RECENTS; } - public boolean onTouchEvent(MotionEvent event) { - boolean result = mTaskSwitcherDetector.onTouchEvent(event); - if (mDockWindowEnabled) { - result |= handleDockWindowEvent(event); - } - return result; - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - float absVelX = Math.abs(velocityX); - float absVelY = Math.abs(velocityY); - boolean isValidFling = absVelX > mMinFlingVelocity && - mIsVertical ? (absVelY > absVelX) : (absVelX > absVelY); - if (isValidFling && mRecentsComponent != null) { - boolean showNext; - if (!mIsRTL) { - showNext = mIsVertical ? (velocityY < 0) : (velocityX < 0); - } else { - // In RTL, vertical is still the same, but horizontal is flipped - showNext = mIsVertical ? (velocityY < 0) : (velocityX > 0); - } - if (showNext) { - mRecentsComponent.showNextAffiliatedTask(); - } else { - mRecentsComponent.showPrevAffiliatedTask(); - } - } - return true; - } - @Override public void onTuningChanged(String key, String newValue) { switch (key) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java index 9f89fe6cb371..989423530599 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java @@ -57,7 +57,7 @@ public class NavigationBarInflaterView extends FrameLayout public static final String NAV_BAR_LEFT = "sysui_nav_bar_left"; public static final String NAV_BAR_RIGHT = "sysui_nav_bar_right"; - public static final String MENU_IME = "menu_ime"; + public static final String MENU_IME_ROTATE = "menu_ime"; public static final String BACK = "back"; public static final String HOME = "home"; public static final String RECENT = "recent"; @@ -319,7 +319,7 @@ public class NavigationBarInflaterView extends FrameLayout String s = Dependency.get(TunerService.class).getValue(NAV_BAR_LEFT, NAVSPACE); button = extractButton(s); } else if (RIGHT.equals(button)) { - String s = Dependency.get(TunerService.class).getValue(NAV_BAR_RIGHT, MENU_IME); + String s = Dependency.get(TunerService.class).getValue(NAV_BAR_RIGHT, MENU_IME_ROTATE); button = extractButton(s); } // Let plugins go first so they can override a standard view if they want. @@ -333,7 +333,7 @@ public class NavigationBarInflaterView extends FrameLayout v = inflater.inflate(R.layout.back, parent, false); } else if (RECENT.equals(button)) { v = inflater.inflate(R.layout.recent_apps, parent, false); - } else if (MENU_IME.equals(button)) { + } else if (MENU_IME_ROTATE.equals(button)) { v = inflater.inflate(R.layout.menu_ime, parent, false); } else if (NAVSPACE.equals(button)) { v = inflater.inflate(R.layout.nav_key_space, parent, false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java index b81a3b0416a4..e09d31cea082 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java @@ -26,7 +26,7 @@ import android.view.IWallpaperVisibilityListener; import android.view.IWindowManager; import android.view.MotionEvent; import android.view.View; -import android.view.WindowManagerGlobal; +import android.view.View.OnLayoutChangeListener; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.Dependency; @@ -37,10 +37,12 @@ public final class NavigationBarTransitions extends BarTransitions { private final NavigationBarView mView; private final IStatusBarService mBarService; private final LightBarTransitionsController mLightTransitionsController; + private final boolean mAllowAutoDimWallpaperNotVisible; private boolean mWallpaperVisible; private boolean mLightsOut; private boolean mAutoDim; + private View mNavButtons; public NavigationBarTransitions(NavigationBarView view) { super(view, R.drawable.nav_background); @@ -49,6 +51,8 @@ public final class NavigationBarTransitions extends BarTransitions { ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mLightTransitionsController = new LightBarTransitionsController(view.getContext(), this::applyDarkIntensity); + mAllowAutoDimWallpaperNotVisible = view.getContext().getResources() + .getBoolean(R.bool.config_navigation_bar_enable_auto_dim_no_visible_wallpaper); IWindowManager windowManagerService = Dependency.get(IWindowManager.class); Handler handler = Handler.getMain(); @@ -64,6 +68,18 @@ public final class NavigationBarTransitions extends BarTransitions { }, Display.DEFAULT_DISPLAY); } catch (RemoteException e) { } + mView.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + View currentView = mView.getCurrentView(); + if (currentView != null) { + mNavButtons = currentView.findViewById(R.id.nav_buttons); + applyLightsOut(false, true); + } + }); + View currentView = mView.getCurrentView(); + if (currentView != null) { + mNavButtons = currentView.findViewById(R.id.nav_buttons); + } } public void init() { @@ -80,8 +96,8 @@ public final class NavigationBarTransitions extends BarTransitions { @Override protected boolean isLightsOut(int mode) { - return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible - && mode != MODE_WARNING); + return super.isLightsOut(mode) || (mAllowAutoDimWallpaperNotVisible && mAutoDim + && !mWallpaperVisible && mode != MODE_WARNING); } public LightBarTransitionsController getLightTransitionsController() { @@ -103,21 +119,20 @@ public final class NavigationBarTransitions extends BarTransitions { if (!force && lightsOut == mLightsOut) return; mLightsOut = lightsOut; - - final View navButtons = mView.getCurrentView().findViewById(R.id.nav_buttons); + if (mNavButtons == null) return; // ok, everyone, stop it right there - navButtons.animate().cancel(); + mNavButtons.animate().cancel(); // Bump percentage by 10% if dark. float darkBump = mLightTransitionsController.getCurrentDarkIntensity() / 10; final float navButtonsAlpha = lightsOut ? 0.6f + darkBump : 1f; if (!animate) { - navButtons.setAlpha(navButtonsAlpha); + mNavButtons.setAlpha(navButtonsAlpha); } else { final int duration = lightsOut ? LIGHTS_OUT_DURATION : LIGHTS_IN_DURATION; - navButtons.animate() + mNavButtons.animate() .alpha(navButtonsAlpha) .setDuration(duration) .start(); @@ -136,6 +151,7 @@ public final class NavigationBarTransitions extends BarTransitions { if (mAutoDim) { applyLightsOut(false, true); } + mView.onDarkIntensityChange(darkIntensity); } private final View.OnTouchListener mLightsOutListener = new View.OnTouchListener() { 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 9a7039a515a1..7994253276ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.LayoutTransition; import android.animation.LayoutTransition.TransitionListener; import android.animation.ObjectAnimator; @@ -26,11 +28,14 @@ import android.app.ActivityManager; import android.app.StatusBarManager; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.drawable.AnimatedVectorDrawable; import android.os.Handler; import android.os.Message; import android.os.RemoteException; +import android.support.annotation.ColorInt; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; @@ -47,15 +52,19 @@ 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; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.statusbar.phone.NavGesture; import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; +import com.android.systemui.recents.RecentsOnboarding; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.policy.DeadZone; import com.android.systemui.statusbar.policy.KeyButtonDrawable; +import com.android.systemui.statusbar.policy.TintedKeyButtonDrawable; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -65,6 +74,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav final static boolean DEBUG = false; final static String TAG = "StatusBar/NavBarView"; + final static int BUTTON_FADE_IN_OUT_DURATION_MS = 100; + // slippery nav bar when everything is disabled, e.g. during setup final static boolean SLIPPERY_WHEN_DISABLED = true; @@ -80,6 +91,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav boolean mShowMenu; boolean mShowAccessibilityButton; boolean mLongClickableAccessibilityButton; + boolean mShowRotateButton; int mDisabledFlags = 0; int mNavigationIconHints = 0; @@ -92,10 +104,12 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private KeyButtonDrawable mImeIcon; private KeyButtonDrawable mMenuIcon; private KeyButtonDrawable mAccessibilityIcon; + private KeyButtonDrawable mRotateSuggestionIcon; private GestureHelper mGestureHelper; private DeadZone mDeadZone; private final NavigationBarTransitions mBarTransitions; + private final OverviewProxyService mOverviewProxyService; // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288) final static boolean WORKAROUND_INVALID_LAYOUT = true; @@ -117,6 +131,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private NavigationBarInflaterView mNavigationInflaterView; private RecentsComponent mRecentsComponent; private Divider mDivider; + private RecentsOnboarding mRecentsOnboarding; + private NotificationPanelView mPanelView; + + private Animator mRotateHideAnimator; private class NavTransitionListener implements TransitionListener { private boolean mBackTransitioning; @@ -208,6 +226,9 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mShowAccessibilityButton = false; mLongClickableAccessibilityButton = false; + mOverviewProxyService = Dependency.get(OverviewProxyService.class); + mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService); + mConfiguration = new Configuration(); mConfiguration.updateFrom(context.getResources().getConfiguration()); updateIcons(context, Configuration.EMPTY, mConfiguration); @@ -221,6 +242,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher)); mButtonDispatchers.put(R.id.accessibility_button, new ButtonDispatcher(R.id.accessibility_button)); + mButtonDispatchers.put(R.id.rotate_suggestion, + new ButtonDispatcher(R.id.rotate_suggestion)); } public BarTransitions getBarTransitions() { @@ -231,9 +254,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav return mBarTransitions.getLightTransitionsController(); } - public void setComponents(RecentsComponent recentsComponent, Divider divider) { + public void setComponents(RecentsComponent recentsComponent, Divider divider, + NotificationPanelView panel) { mRecentsComponent = recentsComponent; mDivider = divider; + mPanelView = panel; if (mGestureHelper instanceof NavigationBarGestureHelper) { ((NavigationBarGestureHelper) mGestureHelper).setComponents( recentsComponent, divider, this); @@ -245,12 +270,16 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav notifyVerticalChangedListener(mVertical); } - @Override - public boolean onTouchEvent(MotionEvent event) { - if (mGestureHelper.onTouchEvent(event)) { - return true; + public void setRecentsAnimationStarted(boolean started) { + if (mRecentsOnboarding != null) { + mRecentsOnboarding.onRecentsAnimationStarted(); } - return super.onTouchEvent(event); + } + + public void onConnectionChanged(boolean isConnected) { + updateSlippery(); + setDisabledFlags(mDisabledFlags, true); + setUpSwipeUpOnboarding(isConnected); } @Override @@ -258,6 +287,14 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav return mGestureHelper.onInterceptTouchEvent(event); } + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mGestureHelper.onTouchEvent(event)) { + return true; + } + return super.onTouchEvent(event); + } + public void abortCurrentGesture() { getHomeButton().abortCurrentGesture(); } @@ -296,10 +333,18 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav return mButtonDispatchers.get(R.id.accessibility_button); } + public ButtonDispatcher getRotateSuggestionButton() { + return mButtonDispatchers.get(R.id.rotate_suggestion); + } + public SparseArray<ButtonDispatcher> getButtonDispatchers() { return mButtonDispatchers; } + public boolean isRecentsButtonVisible() { + return getRecentsButton().getVisibility() == View.VISIBLE; + } + private void updateCarModeIcons(Context ctx) { mBackCarModeIcon = getDrawable(ctx, R.drawable.ic_sysbar_back_carmode, R.drawable.ic_sysbar_back_carmode); @@ -319,14 +364,23 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } if (oldConfig.densityDpi != newConfig.densityDpi || oldConfig.getLayoutDirection() != newConfig.getLayoutDirection()) { - mBackIcon = getDrawable(ctx, R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_dark); + final boolean proxyAvailable = mOverviewProxyService.getProxy() != null; + mBackIcon = proxyAvailable + ? getDrawable(ctx, R.drawable.ic_sysbar_back_quick_step, + R.drawable.ic_sysbar_back_quick_step_dark) + : getDrawable(ctx, R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_dark); mBackLandIcon = mBackIcon; - mBackAltIcon = getDrawable(ctx, - R.drawable.ic_sysbar_back_ime, R.drawable.ic_sysbar_back_ime_dark); + mBackAltIcon = proxyAvailable + ? getDrawable(ctx, R.drawable.ic_sysbar_back_ime_quick_step, + R.drawable.ic_sysbar_back_ime_quick_step_dark) + : getDrawable(ctx, R.drawable.ic_sysbar_back_ime, + R.drawable.ic_sysbar_back_ime_dark); mBackAltLandIcon = mBackAltIcon; - mHomeDefaultIcon = getDrawable(ctx, - R.drawable.ic_sysbar_home, R.drawable.ic_sysbar_home_dark); + mHomeDefaultIcon = proxyAvailable + ? getDrawable(ctx, R.drawable.ic_sysbar_home_quick_step, + R.drawable.ic_sysbar_home_quick_step_dark) + : getDrawable(ctx, R.drawable.ic_sysbar_home, R.drawable.ic_sysbar_home_dark); mRecentIcon = getDrawable(ctx, R.drawable.ic_sysbar_recent, R.drawable.ic_sysbar_recent_dark); mMenuIcon = getDrawable(ctx, R.drawable.ic_sysbar_menu, R.drawable.ic_sysbar_menu_dark); @@ -340,6 +394,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mImeIcon = getDrawable(darkContext, lightContext, R.drawable.ic_ime_switcher_default, R.drawable.ic_ime_switcher_default); + int lightColor = Utils.getColorAttr(lightContext, R.attr.singleToneColor); + int darkColor = Utils.getColorAttr(darkContext, R.attr.singleToneColor); + mRotateSuggestionIcon = getDrawable(ctx, R.drawable.ic_sysbar_rotate_button, + lightColor, darkColor); + if (ALTERNATE_CAR_MODE_UI) { updateCarModeIcons(ctx); } @@ -357,6 +416,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav darkContext.getDrawable(darkIcon)); } + private KeyButtonDrawable getDrawable(Context ctx, @DrawableRes int icon, + @ColorInt int lightColor, @ColorInt int darkColor) { + return TintedKeyButtonDrawable.create(ctx.getDrawable(icon), lightColor, darkColor); + } + @Override public void setLayoutDirection(int layoutDirection) { // Reload all the icons @@ -416,17 +480,22 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav getHomeButton().setImageDrawable(mHomeDefaultIcon); } - // The Accessibility button always overrides the appearance of the IME switcher + // Update IME button visibility, a11y and rotate button always overrides the appearance final boolean showImeButton = - !mShowAccessibilityButton && ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) - != 0); + !mShowAccessibilityButton && + !mShowRotateButton && + ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0); getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE); getImeSwitchButton().setImageDrawable(mImeIcon); - // Update menu button in case the IME state has changed. + // Update menu button, visibility logic in method setMenuVisibility(mShowMenu, true); getMenuButton().setImageDrawable(mMenuIcon); + // Update rotate button, visibility altered by a11y button logic + getRotateSuggestionButton().setImageDrawable(mRotateSuggestionIcon); + + // Update a11y button, visibility logic in state method setAccessibilityButtonState(mShowAccessibilityButton, mLongClickableAccessibilityButton); getAccessibilityButton().setImageDrawable(mAccessibilityIcon); @@ -448,10 +517,22 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav // Always disable recents when alternate car mode UI is active. boolean disableRecent = mUseCarModeUi - || ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0); - final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) + || ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0); + + boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) && ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0); + if ((disableRecent || disableBack) && inScreenPinning()) { + // Don't hide back and recents buttons when in screen pinning mode, as they are used for + // exiting. + disableBack = false; + disableRecent = false; + } + if (mOverviewProxyService.getProxy() != null) { + // When overview is connected to the launcher service, disable the recents button + disableRecent = true; + } + ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons); if (navButtons != null) { LayoutTransition lt = navButtons.getLayoutTransition(); @@ -461,20 +542,16 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } } } - if (inLockTask() && disableRecent && !disableHome) { - // Don't hide recents when in lock task, it is used for exiting. - // Unless home is hidden, then in DPM locked mode and no exit available. - disableRecent = false; - } getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); } - private boolean inLockTask() { + private boolean inScreenPinning() { try { - return ActivityManager.getService().isInLockTaskMode(); + return ActivityManager.getService().getLockTaskModeState() + == ActivityManager.LOCK_TASK_MODE_PINNED; } catch (RemoteException e) { return false; } @@ -527,6 +604,35 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } } + public void onPanelExpandedChange(boolean expanded) { + updateSlippery(); + } + + private void updateSlippery() { + setSlippery(mOverviewProxyService.getProxy() != null && mPanelView.isFullyExpanded()); + } + + private void setSlippery(boolean slippery) { + boolean changed = false; + final ViewGroup navbarView = ((ViewGroup) getParent()); + final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) navbarView + .getLayoutParams(); + if (lp == null) { + return; + } + if (slippery && (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) == 0) { + lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY; + changed = true; + } else if (!slippery && (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0) { + lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY; + changed = true; + } + if (changed) { + WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE); + wm.updateViewLayout(navbarView, lp); + } + } + public void setMenuVisibility(final boolean show) { setMenuVisibility(show, false); } @@ -536,8 +642,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mShowMenu = show; - // Only show Menu if IME switcher and Accessibility button not shown. - final boolean shouldShow = mShowMenu && !mShowAccessibilityButton && + // Only show Menu if IME switcher, rotate and Accessibility buttons are not shown. + final boolean shouldShow = mShowMenu && + !mShowAccessibilityButton && + !mShowRotateButton && ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0); getMenuButton().setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE); @@ -547,15 +655,96 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mShowAccessibilityButton = visible; mLongClickableAccessibilityButton = longClickable; if (visible) { - // Accessibility button overrides Menu and IME switcher buttons. + // Accessibility button overrides Menu, IME switcher and rotate buttons. setMenuVisibility(false, true); getImeSwitchButton().setVisibility(View.INVISIBLE); + setRotateSuggestionButtonState(false, true); } getAccessibilityButton().setVisibility(visible ? View.VISIBLE : View.INVISIBLE); getAccessibilityButton().setLongClickable(longClickable); } + public void setRotateSuggestionButtonState(final boolean visible) { + setRotateSuggestionButtonState(visible, false); + } + + public void setRotateSuggestionButtonState(final boolean visible, final boolean force) { + ButtonDispatcher rotBtn = getRotateSuggestionButton(); + final boolean currentlyVisible = mShowRotateButton; + + // Rerun a show animation to indicate change but don't rerun a hide animation + if (!visible && !currentlyVisible) return; + + View currentView = rotBtn.getCurrentView(); + if (currentView == null) return; + + KeyButtonDrawable kbd = rotBtn.getImageDrawable(); + if (kbd == null) return; + + AnimatedVectorDrawable animIcon = null; + if (kbd.getDrawable(0) instanceof AnimatedVectorDrawable) { + animIcon = (AnimatedVectorDrawable) kbd.getDrawable(0); + } + + if (visible) { // Appear and change, cannot force + setRotateButtonVisibility(true); + + // Stop any currently running hide animations + if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) { + mRotateHideAnimator.pause(); + } + + // Reset the alpha if any has changed due to hide animation + currentView.setAlpha(1f); + + // Run the rotate icon's animation if it has one + if (animIcon != null) { + animIcon.reset(); + animIcon.start(); + } + + } else { // Hide + if (force) { + // If a hide animator is running stop it and instantly make invisible + if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) { + mRotateHideAnimator.pause(); + } + setRotateButtonVisibility(false); + return; + } + + // Don't start any new hide animations if one is running + if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return; + + ObjectAnimator fadeOut = ObjectAnimator.ofFloat(currentView, "alpha", + 0f); + fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS); + fadeOut.setInterpolator(Interpolators.LINEAR); + fadeOut.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + setRotateButtonVisibility(false); + } + }); + + mRotateHideAnimator = fadeOut; + fadeOut.start(); + } + } + + private void setRotateButtonVisibility(final boolean visible) { + // Never show if a11y is visible + final boolean adjVisible = visible && !mShowAccessibilityButton; + final int vis = adjVisible ? View.VISIBLE : View.INVISIBLE; + + getRotateSuggestionButton().setVisibility(vis); + mShowRotateButton = visible; + + // Hide/restore other button visibility, if necessary + setNavigationIconHints(mNavigationIconHints, true); + } + @Override public void onFinishInflate() { mNavigationInflaterView = (NavigationBarInflaterView) findViewById( @@ -568,6 +757,35 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav updateRotatedViews(); } + public void onDarkIntensityChange(float intensity) { + if (mGestureHelper != null) { + mGestureHelper.onDarkIntensityChange(intensity); + } + if (mRecentsOnboarding != null) { + mRecentsOnboarding.setContentDarkIntensity(intensity); + } + } + + public void onOverviewProxyConnectionChanged(boolean isConnected) { + setSlippery(!isConnected); + setDisabledFlags(mDisabledFlags, true); + setUpSwipeUpOnboarding(isConnected); + updateIcons(getContext(), Configuration.EMPTY, mConfiguration); + setNavigationIconHints(mNavigationIconHints, true); + } + + @Override + protected void onDraw(Canvas canvas) { + mGestureHelper.onDraw(canvas); + super.onDraw(canvas); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mGestureHelper.onLayout(changed, left, top, right, bottom); + } + private void updateRotatedViews() { mRotatedViews[Surface.ROTATION_0] = mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0); @@ -664,6 +882,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav updateTaskSwitchHelper(); updateIcons(getContext(), mConfiguration, newConfig); updateRecentsIcon(); + mRecentsOnboarding.onConfigurationChanged(newConfig); if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi || mConfiguration.getLayoutDirection() != newConfig.getLayoutDirection()) { // If car mode or density changes, we need to reset the icons. @@ -752,6 +971,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav onPluginDisconnected(null); // Create default gesture helper Dependency.get(PluginManager.class).addPluginListener(this, NavGesture.class, false /* Only one */); + setUpSwipeUpOnboarding(mOverviewProxyService.getProxy() != null); } @Override @@ -761,6 +981,15 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav if (mGestureHelper != null) { mGestureHelper.destroy(); } + setUpSwipeUpOnboarding(false); + } + + private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) { + if (connectedToOverviewProxy) { + mRecentsOnboarding.onConnectedToLauncher(); + } else { + mRecentsOnboarding.onDisconnectedFromLauncher(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 0f246c621eb9..5cf4c4c70974 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.statusbar.notification.NotificationUtils.isHapticFeedbackDisabled; - import android.content.Context; import android.content.res.Configuration; import android.graphics.Canvas; @@ -102,8 +100,10 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { }.setDuration(200).setDelay(50); public static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5; + public static final int MAX_STATIC_ICONS = 4; + private static final int MAX_DOTS = 3; - private boolean mShowAllIcons = true; + private boolean mIsStaticLayout = true; private final HashMap<View, IconState> mIconStates = new HashMap<>(); private int mDotPadding; private int mStaticDotRadius; @@ -117,19 +117,17 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { private int mSpeedBumpIndex = -1; private int mIconSize; private float mOpenedAmount = 0.0f; - private float mVisualOverflowAdaption; private boolean mDisallowNextAnimation; private boolean mAnimationsEnabled = true; - private boolean mVibrateOnAnimation; - private Vibrator mVibrator; private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons; - private int mDarkOffsetX; + // Keep track of the last visible icon so collapsed container can report on its location + private IconState mLastVisibleIconState; + public NotificationIconContainer(Context context, AttributeSet attrs) { super(context, attrs); initDimens(); setWillNotDraw(!DEBUG); - mVibrator = mContext.getSystemService(Vibrator.class); } private void initDimens() { @@ -168,7 +166,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mIconSize = child.getWidth(); } } - if (mShowAllIcons) { + if (mIsStaticLayout) { resetViewStates(); calculateIconTranslations(); applyIconStates(); @@ -209,7 +207,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex); } } - if (mDark && child instanceof StatusBarIconView) { + if (child instanceof StatusBarIconView) { ((StatusBarIconView) child).setDark(mDark, false, 0); } } @@ -292,7 +290,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { float translationX = getActualPaddingStart(); int firstOverflowIndex = -1; int childCount = getChildCount(); - int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK : childCount; + int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK : + mIsStaticLayout ? MAX_STATIC_ICONS : childCount; float layoutEnd = getLayoutEnd(); float overflowStart = layoutEnd - mIconSize * (2 + OVERFLOW_EARLY_AMOUNT); boolean hasAmbient = mSpeedBumpIndex != -1 && mSpeedBumpIndex < getChildCount(); @@ -325,23 +324,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { visualOverflowStart += (translationX - overflowStart) / mIconSize * (mStaticDotRadius * 2 + mDotPadding); } - if (mShowAllIcons) { - // We want to perfectly position the overflow in the static state, such that - // it's perfectly centered instead of measuring it from the end. - mVisualOverflowAdaption = 0; - if (firstOverflowIndex != -1) { - View firstOverflowView = getChildAt(i); - IconState overflowState = mIconStates.get(firstOverflowView); - float totalAmount = layoutEnd - overflowState.xTranslation; - float newPosition = overflowState.xTranslation + totalAmount / 2 - - totalDotLength / 2 - - mIconSize * 0.5f + mStaticDotRadius; - mVisualOverflowAdaption = newPosition - visualOverflowStart; - visualOverflowStart = newPosition; - } - } else { - visualOverflowStart += mVisualOverflowAdaption * (1f - mOpenedAmount); - } } translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale; } @@ -353,20 +335,24 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { IconState iconState = mIconStates.get(view); int dotWidth = mStaticDotRadius * 2 + mDotPadding; iconState.xTranslation = translationX; - if (numDots <= 3) { + if (numDots <= MAX_DOTS) { if (numDots == 1 && iconState.iconAppearAmount < 0.8f) { iconState.visibleState = StatusBarIconView.STATE_ICON; numDots--; } else { iconState.visibleState = StatusBarIconView.STATE_DOT; } - translationX += (numDots == 3 ? 3 * dotWidth : dotWidth) + translationX += (numDots == MAX_DOTS ? MAX_DOTS * dotWidth : dotWidth) * iconState.iconAppearAmount; + mLastVisibleIconState = iconState; } else { iconState.visibleState = StatusBarIconView.STATE_HIDDEN; } numDots++; } + } else if (childCount > 0) { + View lastChild = getChildAt(childCount - 1); + mLastVisibleIconState = mIconStates.get(lastChild); } boolean center = mDark; if (center && translationX < getLayoutEnd()) { @@ -391,14 +377,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { iconState.xTranslation = getWidth() - iconState.xTranslation - view.getWidth(); } } - - if (mDark && mDarkOffsetX != 0) { - for (int i = 0; i < childCount; i++) { - View view = getChildAt(i); - IconState iconState = mIconStates.get(view); - iconState.xTranslation += mDarkOffsetX; - } - } } private float getLayoutEnd() { @@ -420,13 +398,13 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } /** - * Sets whether the layout should always show all icons. + * Sets whether the layout should always show the same number of icons. * If this is true, the icon positions will be updated on layout. * If this if false, the layout is managed from the outside and layouting won't trigger a * repositioning of the icons. */ - public void setShowAllIcons(boolean showAllIcons) { - mShowAllIcons = showAllIcons; + public void setIsStaticLayout(boolean isStaticLayout) { + mIsStaticLayout = isStaticLayout; } public void setActualLayoutWidth(int actualLayoutWidth) { @@ -457,6 +435,14 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { return mActualLayoutWidth; } + public int getFinalTranslationX() { + if (mLastVisibleIconState == null) { + return 0; + } + + return (int) (mLastVisibleIconState.xTranslation + mIconSize * (1 + OVERFLOW_EARLY_AMOUNT)); + } + public void setChangingViewPositions(boolean changingViewPositions) { mChangingViewPositions = changingViewPositions; } @@ -484,21 +470,41 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mOpenedAmount = expandAmount; } - public float getVisualOverflowAdaption() { - return mVisualOverflowAdaption; - } - - public void setVisualOverflowAdaption(float visualOverflowAdaption) { - mVisualOverflowAdaption = visualOverflowAdaption; - } - public boolean hasOverflow() { + if (mIsStaticLayout) { + return getChildCount() > MAX_STATIC_ICONS; + } + float width = (getChildCount() + OVERFLOW_EARLY_AMOUNT) * mIconSize; return width - (getWidth() - getActualPaddingStart() - getActualPaddingEnd()) > 0; } - public void setVibrateOnAnimation(boolean vibrateOnAnimation) { - mVibrateOnAnimation = vibrateOnAnimation; + /** + * If the overflow is in the range [1, max_dots - 1) (basically 1 or 2 dots), then + * extra padding will have to be accounted for + * + * This method has no meaning for non-static containers + */ + public boolean hasPartialOverflow() { + if (mIsStaticLayout) { + int count = getChildCount(); + return count > MAX_STATIC_ICONS && count <= MAX_STATIC_ICONS + MAX_DOTS; + } + + return false; + } + + /** + * Get padding that can account for extra dots up to the max. The only valid values for + * this method are for 1 or 2 dots. + * @return only extraDotPadding or extraDotPadding * 2 + */ + public int getPartialOverflowExtraPadding() { + if (!hasPartialOverflow()) { + return 0; + } + + return (MAX_STATIC_ICONS + MAX_DOTS - getChildCount()) * (mStaticDotRadius + mDotPadding); } public int getIconSize() { @@ -519,10 +525,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mAnimationsEnabled = enabled; } - public void setDarkOffsetX(int offsetX) { - mDarkOffsetX = offsetX; - } - public void setReplacingIcons(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) { mReplacingIcons = replacingIcons; } @@ -608,39 +610,14 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } else { super.applyToView(view); } - boolean wasInShelf = icon.isInShelf(); boolean inShelf = iconAppearAmount == 1.0f; icon.setIsInShelf(inShelf); - if (shouldVibrateChange(wasInShelf != inShelf)) { - AsyncTask.execute( - () -> mVibrator.vibrate(VibrationEffect.get( - VibrationEffect.EFFECT_TICK))); - } } justAdded = false; justReplaced = false; needsCannedAnimation = false; } - private boolean shouldVibrateChange(boolean inShelfChanged) { - if (!mVibrateOnAnimation) { - return false; - } - if (justAdded) { - return false; - } - if (!mAnimationsEnabled) { - return false; - } - if (!inShelfChanged) { - return false; - } - if (isHapticFeedbackDisabled(mContext)) { - return false; - } - return true; - } - public boolean hasCustomTransformHeight() { return isLastExpandIcon && customTransformHeight != NO_VALUE; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index c19161839998..0e8fcbabf2ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -43,7 +45,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowInsets; -import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; import com.android.internal.logging.MetricsLogger; @@ -65,8 +67,7 @@ import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.notification.NotificationUtils; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; @@ -110,6 +111,7 @@ public class NotificationPanelView extends PanelView implements } }; private final PowerManager mPowerManager; + private final AccessibilityManager mAccessibilityManager; private KeyguardAffordanceHelper mAffordanceHelper; private KeyguardUserSwitcher mKeyguardUserSwitcher; @@ -239,12 +241,17 @@ public class NotificationPanelView extends PanelView implements private ValueAnimator mDarkAnimator; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private boolean mUserSetupComplete; + private int mQsNotificationTopPadding; + private float mExpandOffset; + private boolean mHideIconsDuringNotificationLaunch = true; public NotificationPanelView(Context context, AttributeSet attrs) { super(context, attrs); setWillNotDraw(!DEBUG); mFalsingManager = FalsingManager.getInstance(context); mPowerManager = context.getSystemService(PowerManager.class); + mAccessibilityManager = context.getSystemService(AccessibilityManager.class); + setAccessibilityPaneTitle(determineAccessibilityPaneTitle()); } public void setStatusBar(StatusBar bar) { @@ -307,6 +314,8 @@ public class NotificationPanelView extends PanelView implements R.dimen.max_notification_fadeout_height); mIndicationBottomPadding = getResources().getDimensionPixelSize( R.dimen.keyguard_indication_bottom_padding); + mQsNotificationTopPadding = getResources().getDimensionPixelSize( + R.dimen.qs_notification_padding); } public void updateResources() { @@ -330,7 +339,7 @@ public class NotificationPanelView extends PanelView implements } } - public void onOverlayChanged() { + public void onThemeChanged() { // Re-inflate the status view group. int index = indexOfChild(mKeyguardStatusView); removeView(mKeyguardStatusView); @@ -448,11 +457,12 @@ public class NotificationPanelView extends PanelView implements boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending(); int stackScrollerPadding; if (mStatusBarState != StatusBarState.KEYGUARD) { - stackScrollerPadding = (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight; + stackScrollerPadding = (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight + + mQsNotificationTopPadding; mTopPaddingAdjustment = 0; } else { mClockPositionAlgorithm.setup( - mStatusBar.getMaxKeyguardNotifications(), + mStatusBar.getMaxNotificationsWhileLocked(), getMaxPanelHeight(), getExpandedHeight(), mNotificationStackScroller.getNotGoneChildCount(), @@ -473,7 +483,8 @@ public class NotificationPanelView extends PanelView implements mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment; } mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding); - mNotificationStackScroller.setDarkShelfOffsetX(mClockPositionResult.clockX); + mNotificationStackScroller.setAntiBurnInOffsetX(mClockPositionResult.clockX); + mKeyguardBottomArea.setBurnInXOffset(mClockPositionResult.clockX); requestScrollerTopPaddingUpdate(animate); } @@ -503,7 +514,8 @@ public class NotificationPanelView extends PanelView implements if (suppressedSummary) { continue; } - if (!mStatusBar.shouldShowOnKeyguard(row.getStatusBarNotification())) { + if (!mStatusBar.getNotificationLockscreenUserManager().shouldShowOnKeyguard( + row.getStatusBarNotification())) { continue; } if (row.isRemoved()) { @@ -596,7 +608,7 @@ public class NotificationPanelView extends PanelView implements mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE; } closeQs(); - mStatusBar.closeAndSaveGuts(true /* leavebehind */, true /* force */, + mStatusBar.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */, true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */); mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */, true /* cancelAnimators */); @@ -652,22 +664,12 @@ public class NotificationPanelView extends PanelView implements } @Override - public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { - if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { - event.getText().add(getKeyguardOrLockScreenString()); - mLastAnnouncementWasQuickSettings = false; - return true; - } - return super.dispatchPopulateAccessibilityEventInternal(event); - } - - @Override public boolean onInterceptTouchEvent(MotionEvent event) { - if (mBlockTouches || mQs.isCustomizing()) { + if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) { return false; } initDownStates(event); - if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { + if (mBar.panelEnabled() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { mIsExpansionFromHeadsUp = true; MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1); MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1); @@ -704,7 +706,7 @@ public class NotificationPanelView extends PanelView implements mInitialHeightOnTouch = mQsExpansionHeight; mQsTracking = true; mIntercepting = false; - mNotificationStackScroller.removeLongPressCallback(); + mNotificationStackScroller.cancelLongPress(); } break; case MotionEvent.ACTION_POINTER_UP: @@ -740,7 +742,7 @@ public class NotificationPanelView extends PanelView implements mInitialTouchY = y; mInitialTouchX = x; mIntercepting = false; - mNotificationStackScroller.removeLongPressCallback(); + mNotificationStackScroller.cancelLongPress(); return true; } break; @@ -818,7 +820,7 @@ public class NotificationPanelView extends PanelView implements private float getQsExpansionFraction() { return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight) - / (getTempQsMaxExpansion() - mQsMinExpansionHeight)); + / (mQsMaxExpansionHeight - mQsMinExpansionHeight)); } @Override @@ -1291,10 +1293,6 @@ public class NotificationPanelView extends PanelView implements setQsExpanded(true); } else if (height <= mQsMinExpansionHeight && mQsExpanded) { setQsExpanded(false); - if (mLastAnnouncementWasQuickSettings && !mTracking && !isCollapsing()) { - announceForAccessibility(getKeyguardOrLockScreenString()); - mLastAnnouncementWasQuickSettings = false; - } } mQsExpansionHeight = height; updateQsExpansion(); @@ -1320,13 +1318,10 @@ public class NotificationPanelView extends PanelView implements updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale); } - // Upon initialisation when we are not layouted yet we don't want to announce that we are - // fully expanded, hence the != 0.0f check. - if (height != 0.0f && mQsFullyExpanded && !mLastAnnouncementWasQuickSettings) { - announceForAccessibility(getContext().getString( - R.string.accessibility_desc_quick_settings)); - mLastAnnouncementWasQuickSettings = true; + if (mAccessibilityManager.isEnabled()) { + setAccessibilityPaneTitle(determineAccessibilityPaneTitle()); } + if (mQsFullyExpanded && mFalsingManager.shouldEnforceBouncer()) { mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */, false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */); @@ -1341,9 +1336,13 @@ public class NotificationPanelView extends PanelView implements mQs.setQsExpansion(getQsExpansionFraction(), getHeaderTranslation()); } - private String getKeyguardOrLockScreenString() { + private String determineAccessibilityPaneTitle() { if (mQs != null && mQs.isCustomizing()) { return getContext().getString(R.string.accessibility_desc_quick_settings_edit); + } else if (mQsExpansionHeight != 0.0f && mQsFullyExpanded) { + // Upon initialisation when we are not layouted yet we don't want to announce that we + // are fully expanded, hence the != 0.0f check. + return getContext().getString(R.string.accessibility_desc_quick_settings); } else if (mStatusBarState == StatusBarState.KEYGUARD) { return getContext().getString(R.string.accessibility_desc_lock_screen); } else { @@ -1361,7 +1360,7 @@ public class NotificationPanelView extends PanelView implements // take the maximum and linearly interpolate with the panel expansion for a nice motion. int maxNotifications = mClockPositionResult.stackScrollerPadding - mClockPositionResult.stackScrollerPaddingAdjustment; - int maxQs = getTempQsMaxExpansion(); + int maxQs = mQsMaxExpansionHeight + mQsNotificationTopPadding; int max = mStatusBarState == StatusBarState.KEYGUARD ? Math.max(maxNotifications, maxQs) : maxQs; @@ -1375,9 +1374,9 @@ public class NotificationPanelView extends PanelView implements // from a scrolled quick settings. return interpolate(getQsExpansionFraction(), mNotificationStackScroller.getIntrinsicPadding(), - mQsMaxExpansionHeight); + mQsMaxExpansionHeight + mQsNotificationTopPadding); } else { - return mQsExpansionHeight; + return mQsExpansionHeight + mQsNotificationTopPadding; } } @@ -1544,7 +1543,7 @@ public class NotificationPanelView extends PanelView implements / (panelHeightQsExpanded - panelHeightQsCollapsed); } setQsExpansion(mQsMinExpansionHeight - + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight)); + + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight)); } updateExpandedHeight(expandedHeight); updateHeader(); @@ -1560,20 +1559,12 @@ public class NotificationPanelView extends PanelView implements private void updatePanelExpanded() { boolean isExpanded = !isFullyCollapsed(); if (mPanelExpanded != isExpanded) { - mHeadsUpManager.setIsExpanded(isExpanded); + mHeadsUpManager.setIsPanelExpanded(isExpanded); mStatusBar.setPanelExpanded(isExpanded); mPanelExpanded = isExpanded; } } - /** - * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when - * collapsing QS / the panel when QS was scrolled - */ - private int getTempQsMaxExpansion() { - return mQsMaxExpansionHeight; - } - private int calculatePanelHeightShade() { int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin(); int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin @@ -1596,6 +1587,10 @@ public class NotificationPanelView extends PanelView implements } int maxQsHeight = mQsMaxExpansionHeight; + if (mKeyguardShowing) { + maxQsHeight += mQsNotificationTopPadding; + } + // If an animation is changing the size of the QS panel, take the animated value. if (mQsSizeChangeAnimator != null) { maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue(); @@ -1672,8 +1667,9 @@ public class NotificationPanelView extends PanelView implements if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { return 0; } - float translation = NotificationUtils.interpolate(-mQsMinExpansionHeight, 0, - mNotificationStackScroller.getAppearFraction(mExpandedHeight)); + float translation = MathUtils.lerp(-mQsMinExpansionHeight, 0, + Math.min(1.0f, mNotificationStackScroller.getAppearFraction(mExpandedHeight))) + + mExpandOffset; return Math.min(0, translation); } @@ -1874,6 +1870,9 @@ public class NotificationPanelView extends PanelView implements requestScrollerTopPaddingUpdate(false /* animate */); requestPanelHeightUpdate(); } + if (mAccessibilityManager.isEnabled()) { + setAccessibilityPaneTitle(determineAccessibilityPaneTitle()); + } } @Override @@ -2330,7 +2329,7 @@ public class NotificationPanelView extends PanelView implements } @Override - public void setHeadsUpManager(HeadsUpManager headsUpManager) { + public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { super.setHeadsUpManager(headsUpManager); mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller, this); @@ -2537,6 +2536,9 @@ public class NotificationPanelView extends PanelView implements } public boolean hideStatusBarIconsWhenExpanded() { + if (mLaunchingNotification) { + return mHideIconsDuringNotificationLaunch; + } return !isFullWidth() || !mShowIconsWhenExpanded; } @@ -2607,7 +2609,8 @@ public class NotificationPanelView extends PanelView implements private void setDarkAmount(float amount) { mDarkAmount = amount; - mKeyguardStatusView.setDark(mDarkAmount); + mKeyguardStatusView.setDarkAmount(mDarkAmount); + mKeyguardBottomArea.setDarkAmount(mDarkAmount); positionClockAndNotifications(); } @@ -2620,6 +2623,9 @@ public class NotificationPanelView extends PanelView implements public void setPulsing(boolean pulsing) { mKeyguardStatusView.setPulsing(pulsing); + positionClockAndNotifications(); + mNotificationStackScroller.setPulsing(pulsing, mKeyguardStatusView.getLocationOnScreen()[1] + + mKeyguardStatusView.getClockBottom()); } public void setAmbientIndicationBottomPadding(int ambientIndicationBottomPadding) { @@ -2629,8 +2635,9 @@ public class NotificationPanelView extends PanelView implements } } - public void refreshTime() { + public void dozeTimeTick() { mKeyguardStatusView.refreshTime(); + mKeyguardBottomArea.dozeTimeTick(); if (mDarkAmount > 0) { positionClockAndNotifications(); } @@ -2658,4 +2665,19 @@ public class NotificationPanelView extends PanelView implements public LockIcon getLockIcon() { return mKeyguardBottomArea.getLockIcon(); } + + public void applyExpandAnimationParams(ExpandAnimationParameters params) { + mExpandOffset = params != null ? params.getTopChange() : 0; + updateQsExpansion(); + if (params != null) { + boolean hideIcons = params.getProgress( + ActivityLaunchAnimator.ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f; + if (hideIcons != mHideIconsDuringNotificationLaunch) { + mHideIconsDuringNotificationLaunch = hideIcons; + if (!hideIcons) { + mStatusBar.recomputeDisableFlags(true /* animate */); + } + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index afe5c917a856..6daabede7f32 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.statusbar.notification.NotificationUtils.isHapticFeedbackDisabled; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -25,7 +23,9 @@ import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.database.ContentObserver; import android.os.AsyncTask; +import android.os.Handler; import android.os.SystemClock; import android.os.UserHandle; import android.os.VibrationEffect; @@ -42,7 +42,7 @@ import android.view.animation.Interpolator; import android.widget.FrameLayout; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.keyguard.LatencyTracker; +import com.android.internal.util.LatencyTracker; import com.android.systemui.DejankUtils; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -50,8 +50,7 @@ import com.android.systemui.classifier.FalsingManager; import com.android.systemui.doze.DozeLog; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.notification.NotificationUtils; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -61,18 +60,22 @@ public abstract class PanelView extends FrameLayout { public static final String TAG = PanelView.class.getSimpleName(); private static final int INITIAL_OPENING_PEEK_DURATION = 200; private static final int PEEK_ANIMATION_DURATION = 360; + private static final int NO_FIXED_DURATION = -1; private long mDownTime; private float mMinExpandHeight; private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); private boolean mPanelUpdateWhenAnimatorEnds; private boolean mVibrateOnOpening; + private boolean mVibrationEnabled; + protected boolean mLaunchingNotification; + private int mFixedDuration = NO_FIXED_DURATION; private final void logf(String fmt, Object... args) { Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); } protected StatusBar mStatusBar; - protected HeadsUpManager mHeadsUpManager; + protected HeadsUpManagerPhone mHeadsUpManager; private float mPeekHeight; private float mHintDistance; @@ -108,6 +111,12 @@ public abstract class PanelView extends FrameLayout { private FlingAnimationUtils mFlingAnimationUtilsDismissing; private FalsingManager mFalsingManager; private final Vibrator mVibrator; + final private ContentObserver mVibrationObserver = new ContentObserver(Handler.getMain()) { + @Override + public void onChange(boolean selfChange) { + updateHapticFeedBackEnabled(); + } + }; /** * Whether an instant expand request is currently pending and we are just waiting for layout. @@ -212,6 +221,15 @@ public abstract class PanelView extends FrameLayout { mVibrator = mContext.getSystemService(Vibrator.class); mVibrateOnOpening = mContext.getResources().getBoolean( R.bool.config_vibrateOnIconAnimation); + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_ENABLED), true, + mVibrationObserver); + mVibrationObserver.onChange(false /* selfChange */); + } + + public void updateHapticFeedBackEnabled() { + mVibrationEnabled = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) != 0; } protected void loadDimens() { @@ -403,7 +421,7 @@ public abstract class PanelView extends FrameLayout { runPeekAnimation(INITIAL_OPENING_PEEK_DURATION, getOpeningHeight(), false /* collapseWhenFinished */); notifyBarPanelExpansionChanged(); - if (mVibrateOnOpening && !isHapticFeedbackDisabled(mContext)) { + if (mVibrateOnOpening && mVibrationEnabled) { AsyncTask.execute(() -> mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_TICK, false))); } @@ -770,6 +788,9 @@ public abstract class PanelView extends FrameLayout { if (vel == 0) { animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor)); } + if (mFixedDuration != NO_FIXED_DURATION) { + animator.setDuration(mFixedDuration); + } } animator.addListener(new AnimatorListenerAdapter() { private boolean mCancelled; @@ -929,7 +950,7 @@ public abstract class PanelView extends FrameLayout { } public boolean isCollapsing() { - return mClosing; + return mClosing || mLaunchingNotification; } public boolean isTracking() { @@ -1231,7 +1252,17 @@ public abstract class PanelView extends FrameLayout { */ protected abstract int getClearAllHeight(); - public void setHeadsUpManager(HeadsUpManager headsUpManager) { + public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; } + + public void setLaunchingNotification(boolean launchingNotification) { + mLaunchingNotification = launchingNotification; + } + + public void collapseWithDuration(int animationDuration) { + mFixedDuration = animationDuration; + collapse(false /* delayed */, 1.0f /* speedUpFactor */); + mFixedDuration = NO_FIXED_DURATION; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 4ae13936d1eb..6444cc816663 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -16,8 +16,12 @@ package com.android.systemui.statusbar.phone; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; + import android.app.ActivityManager; -import android.app.ActivityManager.StackId; import android.app.ActivityManager.StackInfo; import android.app.AlarmManager; import android.app.AlarmManager.AlarmClockInfo; @@ -35,7 +39,6 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; -import android.content.pm.UserInfo; import android.graphics.drawable.Icon; import android.media.AudioManager; import android.net.Uri; @@ -61,8 +64,8 @@ import com.android.systemui.SysUiServiceProvider; import com.android.systemui.UiOffloadThread; import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.qs.tiles.RotationLockTile; -import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; +import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.policy.BluetoothController; @@ -141,7 +144,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, private boolean mDockedStackExists; private boolean mManagedProfileIconVisible = false; - private boolean mManagedProfileInQuietMode = false; private BluetoothController mBluetooth; @@ -245,7 +247,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, mLocationController.addCallback(this); SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this); - SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskListener); + ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener); // Clear out all old notifications on startup (only present in the case where sysui dies) NotificationManager noMan = mContext.getSystemService(NotificationManager.class); @@ -405,17 +407,17 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, int iconId = R.drawable.stat_sys_data_bluetooth; String contentDescription = mContext.getString(R.string.accessibility_quick_settings_bluetooth_on); - boolean bluetoothEnabled = false; + boolean bluetoothVisible = false; if (mBluetooth != null) { - bluetoothEnabled = mBluetooth.isBluetoothEnabled(); if (mBluetooth.isBluetoothConnected()) { iconId = R.drawable.stat_sys_data_bluetooth_connected; contentDescription = mContext.getString(R.string.accessibility_bluetooth_connected); + bluetoothVisible = mBluetooth.isBluetoothEnabled(); } } mIconController.setIcon(mSlotBluetooth, iconId, contentDescription); - mIconController.setIconVisibility(mSlotBluetooth, bluetoothEnabled); + mIconController.setIconVisibility(mSlotBluetooth, bluetoothVisible); } private final void updateTTY() { @@ -469,17 +471,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, } } - private void updateQuietState() { - mManagedProfileInQuietMode = false; - int currentUserId = ActivityManager.getCurrentUser(); - for (UserInfo ui : mUserManager.getEnabledProfiles(currentUserId)) { - if (ui.isManagedProfile() && ui.isQuietModeEnabled()) { - mManagedProfileInQuietMode = true; - return; - } - } - } - private void updateManagedProfile() { // getLastResumedActivityUserId needds to acquire the AM lock, which may be contended in // some cases. Since it doesn't really matter here whether it's updated in this frame @@ -497,11 +488,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, mIconController.setIcon(mSlotManagedProfile, R.drawable.stat_sys_managed_profile_status, mContext.getString(R.string.accessibility_managed_profile)); - } else if (mManagedProfileInQuietMode) { - showIcon = true; - mIconController.setIcon(mSlotManagedProfile, - R.drawable.stat_sys_managed_profile_status_off, - mContext.getString(R.string.accessibility_managed_profile)); } else { showIcon = false; } @@ -523,12 +509,18 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, mCurrentNotifs.clear(); mUiOffloadThread.submit(() -> { try { - int focusedId = ActivityManager.getService().getFocusedStackId(); - if (focusedId == StackId.FULLSCREEN_WORKSPACE_STACK_ID) { - checkStack(StackId.FULLSCREEN_WORKSPACE_STACK_ID, notifs, noMan, pm); + final StackInfo focusedStack = ActivityManager.getService().getFocusedStackInfo(); + if (focusedStack != null) { + final int windowingMode = + focusedStack.configuration.windowConfiguration.getWindowingMode(); + if (windowingMode == WINDOWING_MODE_FULLSCREEN + || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { + checkStack(focusedStack, notifs, noMan, pm); + } } if (mDockedStackExists) { - checkStack(StackId.DOCKED_STACK_ID, notifs, noMan, pm); + checkStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED, + notifs, noMan, pm); } } catch (RemoteException e) { e.rethrowFromSystemServer(); @@ -539,10 +531,19 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, }); } - private void checkStack(int stackId, ArraySet<Pair<String, Integer>> notifs, + private void checkStack(int windowingMode, int activityType, + ArraySet<Pair<String, Integer>> notifs, NotificationManager noMan, IPackageManager pm) { + try { + final StackInfo info = + ActivityManager.getService().getStackInfo(windowingMode, activityType); + checkStack(info, notifs, noMan, pm); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + private void checkStack(StackInfo info, ArraySet<Pair<String, Integer>> notifs, NotificationManager noMan, IPackageManager pm) { try { - StackInfo info = ActivityManager.getService().getStackInfo(stackId); if (info == null || info.topActivity == null) return; String pkg = info.topActivity.getPackageName(); if (!hasNotif(notifs, pkg, info.userId)) { @@ -595,6 +596,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, .addCategory(Intent.CATEGORY_BROWSABLE) .addCategory("unique:" + System.currentTimeMillis()) .putExtra(Intent.EXTRA_PACKAGE_NAME, appInfo.packageName) + .putExtra(Intent.EXTRA_VERSION_CODE, (int) (appInfo.versionCode & 0x7fffffff)) .putExtra(Intent.EXTRA_VERSION_CODE, appInfo.versionCode) .putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, pendingIntent); @@ -620,12 +622,17 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, } private Intent getTaskIntent(int taskId, int userId) { - List<ActivityManager.RecentTaskInfo> tasks = mContext.getSystemService(ActivityManager.class) - .getRecentTasksForUser(NUM_TASKS_FOR_INSTANT_APP_INFO, 0, userId); - for (int i = 0; i < tasks.size(); i++) { - if (tasks.get(i).id == taskId) { - return tasks.get(i).baseIntent; + try { + final List<ActivityManager.RecentTaskInfo> tasks = + ActivityManager.getService().getRecentTasks( + NUM_TASKS_FOR_INSTANT_APP_INFO, 0, userId).getList(); + for (int i = 0; i < tasks.size(); i++) { + if (tasks.get(i).id == taskId) { + return tasks.get(i).baseIntent; + } } + } catch (RemoteException e) { + // Fall through } return null; } @@ -650,7 +657,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, public void onUserSwitchComplete(int newUserId) throws RemoteException { mHandler.post(() -> { updateAlarm(); - updateQuietState(); updateManagedProfile(); updateForegroundInstantApps(); }); @@ -659,7 +665,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, private final HotspotController.Callback mHotspotCallback = new HotspotController.Callback() { @Override - public void onHotspotChanged(boolean enabled) { + public void onHotspotChanged(boolean enabled, int numDevices) { mIconController.setIconVisibility(mSlotHotspot, enabled); } }; @@ -698,7 +704,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, if (mCurrentUserSetup == userSetup) return; mCurrentUserSetup = userSetup; updateAlarm(); - updateQuietState(); } @Override @@ -744,7 +749,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, mIconController.setIconVisibility(mSlotDataSaver, isDataSaving); } - private final TaskStackListener mTaskListener = new TaskStackListener() { + private final SysUiTaskStackChangeListener mTaskListener = new SysUiTaskStackChangeListener() { @Override public void onTaskStackChanged() { // Listen for changes to stacks and then check which instant apps are foreground. @@ -767,7 +772,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, } else if (action.equals(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) || action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) || action.equals(Intent.ACTION_MANAGED_PROFILE_REMOVED)) { - updateQuietState(); updateManagedProfile(); } else if (action.equals(AudioManager.ACTION_HEADSET_PLUG)) { updateHeadsetPlug(intent); 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 970d1de251d5..cc5a93ce71b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -16,26 +16,34 @@ package com.android.systemui.statusbar.phone; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; + +import android.annotation.Nullable; import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Rect; import android.util.AttributeSet; import android.util.EventLog; +import android.view.DisplayCutout; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; -import com.android.systemui.BatteryMeterView; -import com.android.systemui.DejankUtils; +import android.widget.FrameLayout; +import android.widget.LinearLayout; import com.android.systemui.Dependency; import com.android.systemui.EventLogTags; import com.android.systemui.R; import com.android.systemui.statusbar.policy.DarkIconDispatcher; import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.util.leak.RotationUtils; public class PhoneStatusBarView extends PanelBar { private static final String TAG = "PhoneStatusBarView"; private static final boolean DEBUG = StatusBar.DEBUG; private static final boolean DEBUG_GESTURES = false; + private static final int NO_VALUE = Integer.MIN_VALUE; StatusBar mBar; @@ -53,6 +61,11 @@ public class PhoneStatusBarView extends PanelBar { } }; private DarkReceiver mBattery; + private int mLastOrientation; + @Nullable + private View mCutoutSpace; + @Nullable + private DisplayCutout mDisplayCutout; public PhoneStatusBarView(Context context, AttributeSet attrs) { super(context, attrs); @@ -76,6 +89,7 @@ public class PhoneStatusBarView extends PanelBar { public void onFinishInflate() { mBarTransitions.init(); mBattery = findViewById(R.id.battery); + mCutoutSpace = findViewById(R.id.cutout_space_view); } @Override @@ -83,12 +97,51 @@ public class PhoneStatusBarView extends PanelBar { super.onAttachedToWindow(); // Always have Battery meters in the status bar observe the dark/light modes. Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery); + if (updateOrientationAndCutout(getResources().getConfiguration().orientation)) { + postUpdateLayoutForCutout(); + } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mBattery); + mDisplayCutout = null; + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + // May trigger cutout space layout-ing + if (updateOrientationAndCutout(newConfig.orientation)) { + postUpdateLayoutForCutout(); + } + } + + /** + * + * @param newOrientation may pass NO_VALUE for no change + * @return boolean indicating if we need to update the cutout location / margins + */ + private boolean updateOrientationAndCutout(int newOrientation) { + boolean changed = false; + if (newOrientation != NO_VALUE) { + if (mLastOrientation != newOrientation) { + changed = true; + mLastOrientation = newOrientation; + } + } + + if (mDisplayCutout == null) { + DisplayCutout cutout = getRootWindowInsets().getDisplayCutout(); + if (cutout != null) { + changed = true; + mDisplayCutout = cutout; + } + } + + return changed; } @Override @@ -197,6 +250,9 @@ public class PhoneStatusBarView extends PanelBar { super.panelExpansionChanged(frac, expanded); mPanelFraction = frac; updateScrimFraction(); + if ((frac == 0 || frac == 1) && mBar.getNavigationBarView() != null) { + mBar.getNavigationBarView().onPanelExpandedChange(expanded); + } } private void updateScrimFraction() { @@ -214,4 +270,80 @@ public class PhoneStatusBarView extends PanelBar { R.dimen.status_bar_height); setLayoutParams(layoutParams); } + + private void updateLayoutForCutout() { + updateCutoutLocation(); + updateSafeInsets(); + } + + private void postUpdateLayoutForCutout() { + Runnable r = new Runnable() { + @Override + public void run() { + updateLayoutForCutout(); + } + }; + // Let the cutout emulation draw first + postDelayed(r, 0); + } + + private void updateCutoutLocation() { + // Not all layouts have a cutout (e.g., Car) + if (mCutoutSpace == null) { + return; + } + + if (mDisplayCutout == null || mDisplayCutout.isEmpty() + || mLastOrientation != ORIENTATION_PORTRAIT) { + mCutoutSpace.setVisibility(View.GONE); + return; + } + + mCutoutSpace.setVisibility(View.VISIBLE); + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mCutoutSpace.getLayoutParams(); + lp.width = mDisplayCutout.getBoundingRect().width(); + lp.height = mDisplayCutout.getBoundingRect().height(); + } + + private void updateSafeInsets() { + // Depending on our rotation, we may have to work around a cutout in the middle of the view, + // or letterboxing from the right or left sides. + + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); + if (mDisplayCutout == null || mDisplayCutout.isEmpty()) { + lp.leftMargin = 0; + lp.rightMargin = 0; + return; + } + + int leftMargin = 0; + int rightMargin = 0; + switch (RotationUtils.getRotation(getContext())) { + /* + * Landscape: <-| + * Seascape: |-> + */ + case RotationUtils.ROTATION_LANDSCAPE: + leftMargin = getDisplayCutoutHeight(); + break; + case RotationUtils.ROTATION_SEASCAPE: + rightMargin = getDisplayCutoutHeight(); + break; + default: + break; + } + + lp.leftMargin = leftMargin; + lp.rightMargin = rightMargin; + } + + //TODO: Find a better way + private int getDisplayCutoutHeight() { + if (mDisplayCutout == null || mDisplayCutout.isEmpty()) { + return 0; + } + + Rect r = mDisplayCutout.getBoundingRect(); + return r.bottom - r.top; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java new file mode 100644 index 000000000000..dc0835e4371a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java @@ -0,0 +1,431 @@ +/* + * 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.phone; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ArgbEvaluator; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.os.Handler; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.util.Log; +import android.util.Slog; +import android.view.Display; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.support.annotation.DimenRes; +import com.android.systemui.Dependency; +import com.android.systemui.OverviewProxyService; +import com.android.systemui.R; +import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; +import com.android.systemui.shared.recents.IOverviewProxy; +import com.android.systemui.shared.recents.utilities.Utilities; + +import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM; +import static com.android.systemui.OverviewProxyService.DEBUG_OVERVIEW_PROXY; +import static com.android.systemui.OverviewProxyService.TAG_OPS; + +/** + * Class to detect gestures on the navigation bar and implement quick scrub and switch. + */ +public class QuickScrubController extends GestureDetector.SimpleOnGestureListener implements + GestureHelper { + + private static final String TAG = "QuickScrubController"; + private static final int QUICK_SWITCH_FLING_VELOCITY = 0; + private static final int ANIM_DURATION_MS = 200; + private static final long LONG_PRESS_DELAY_MS = 225; + + /** + * For quick step, set a damping value to allow the button to stick closer its origin position + * when dragging before quick scrub is active. + */ + private static final int SWITCH_STICKINESS = 4; + + private NavigationBarView mNavigationBarView; + private GestureDetector mGestureDetector; + + private boolean mDraggingActive; + private boolean mQuickScrubActive; + private boolean mAllowQuickSwitch; + private float mDownOffset; + private float mTranslation; + private int mTouchDownX; + private int mTouchDownY; + private boolean mDragPositive; + private boolean mIsVertical; + private boolean mIsRTL; + private float mTrackAlpha; + private int mLightTrackColor; + private int mDarkTrackColor; + private float mDarkIntensity; + + private final Handler mHandler = new Handler(); + private final Interpolator mQuickScrubEndInterpolator = new DecelerateInterpolator(); + private final Rect mTrackRect = new Rect(); + private final Rect mHomeButtonRect = new Rect(); + private final Paint mTrackPaint = new Paint(); + private final int mScrollTouchSlop; + private final OverviewProxyService mOverviewEventSender; + private final int mTrackThickness; + private final int mTrackPadding; + private final ValueAnimator mTrackAnimator; + private final ValueAnimator mButtonAnimator; + private final AnimatorSet mQuickScrubEndAnimator; + private final Context mContext; + private final ArgbEvaluator mTrackColorEvaluator = new ArgbEvaluator(); + + private final AnimatorUpdateListener mTrackAnimatorListener = valueAnimator -> { + mTrackAlpha = (float) valueAnimator.getAnimatedValue(); + mNavigationBarView.invalidate(); + }; + + private final AnimatorUpdateListener mButtonTranslationListener = animator -> { + int pos = (int) animator.getAnimatedValue(); + if (!mQuickScrubActive) { + pos = mDragPositive ? Math.min((int) mTranslation, pos) : Math.max((int) mTranslation, pos); + } + final View homeView = mNavigationBarView.getHomeButton().getCurrentView(); + if (mIsVertical) { + homeView.setTranslationY(pos); + } else { + homeView.setTranslationX(pos); + } + }; + + private AnimatorListenerAdapter mQuickScrubEndListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mNavigationBarView.getHomeButton().setClickable(true); + mQuickScrubActive = false; + mTranslation = 0; + } + }; + + private Runnable mLongPressRunnable = this::startQuickScrub; + + private final GestureDetector.SimpleOnGestureListener mGestureListener = + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velX, float velY) { + if (!isQuickScrubEnabled() || mQuickScrubActive || !mAllowQuickSwitch || + !mHomeButtonRect.contains(mTouchDownX, mTouchDownY)) { + return false; + } + float velocityX = mIsRTL ? -velX : velX; + float absVelY = Math.abs(velY); + final boolean isValidFling = velocityX > QUICK_SWITCH_FLING_VELOCITY && + mIsVertical ? (absVelY > velocityX) : (velocityX > absVelY); + if (isValidFling) { + mDraggingActive = false; + mButtonAnimator.setIntValues((int) mTranslation, 0); + mButtonAnimator.start(); + mHandler.removeCallbacks(mLongPressRunnable); + try { + final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy(); + overviewProxy.onQuickSwitch(); + if (DEBUG_OVERVIEW_PROXY) { + Log.d(TAG_OPS, "Quick Switch"); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to send start of quick switch.", e); + } + return true; + } + return false; + } + }; + + public QuickScrubController(Context context) { + mContext = context; + mScrollTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mOverviewEventSender = Dependency.get(OverviewProxyService.class); + mGestureDetector = new GestureDetector(mContext, mGestureListener); + mTrackThickness = getDimensionPixelSize(mContext, R.dimen.nav_quick_scrub_track_thickness); + mTrackPadding = getDimensionPixelSize(mContext, R.dimen.nav_quick_scrub_track_edge_padding); + mTrackPaint.setAlpha(0); + + mTrackAnimator = ObjectAnimator.ofFloat(); + mTrackAnimator.addUpdateListener(mTrackAnimatorListener); + mButtonAnimator = ObjectAnimator.ofInt(); + mButtonAnimator.addUpdateListener(mButtonTranslationListener); + mQuickScrubEndAnimator = new AnimatorSet(); + mQuickScrubEndAnimator.playTogether(mTrackAnimator, mButtonAnimator); + mQuickScrubEndAnimator.setDuration(ANIM_DURATION_MS); + mQuickScrubEndAnimator.addListener(mQuickScrubEndListener); + mQuickScrubEndAnimator.setInterpolator(mQuickScrubEndInterpolator); + } + + public void setComponents(NavigationBarView navigationBarView) { + mNavigationBarView = navigationBarView; + } + + /** + * @return true if we want to intercept touch events for quick scrub/switch and prevent proxying + * the event to the overview service. + */ + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy(); + final ButtonDispatcher homeButton = mNavigationBarView.getHomeButton(); + if (overviewProxy == null) { + homeButton.setDelayTouchFeedback(false); + return false; + } + + return handleTouchEvent(event); + } + + /** + * @return true if we want to handle touch events for quick scrub/switch and prevent proxying + * the event to the overview service. + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + return handleTouchEvent(event); + } + + private boolean handleTouchEvent(MotionEvent event) { + final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy(); + final ButtonDispatcher homeButton = mNavigationBarView.getHomeButton(); + if (mGestureDetector.onTouchEvent(event)) { + // If the fling has been handled on UP, then skip proxying the UP + return true; + } + int action = event.getAction(); + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: { + int x = (int) event.getX(); + int y = (int) event.getY(); + if (isQuickScrubEnabled() && mHomeButtonRect.contains(x, y)) { + mTouchDownX = x; + mTouchDownY = y; + homeButton.setDelayTouchFeedback(true); + mHandler.postDelayed(mLongPressRunnable, LONG_PRESS_DELAY_MS); + } else { + homeButton.setDelayTouchFeedback(false); + mTouchDownX = mTouchDownY = -1; + } + mAllowQuickSwitch = true; + break; + } + case MotionEvent.ACTION_MOVE: { + if (mTouchDownX != -1) { + int x = (int) event.getX(); + int y = (int) event.getY(); + int xDiff = Math.abs(x - mTouchDownX); + int yDiff = Math.abs(y - mTouchDownY); + boolean exceededTouchSlopX = xDiff > mScrollTouchSlop && xDiff > yDiff; + boolean exceededTouchSlopY = yDiff > mScrollTouchSlop && yDiff > xDiff; + boolean exceededTouchSlop, exceededPerpendicularTouchSlop; + int pos, touchDown, offset, trackSize; + + if (mIsVertical) { + exceededTouchSlop = exceededTouchSlopY; + exceededPerpendicularTouchSlop = exceededTouchSlopX; + pos = y; + touchDown = mTouchDownY; + offset = pos - mTrackRect.top; + trackSize = mTrackRect.height(); + } else { + exceededTouchSlop = exceededTouchSlopX; + exceededPerpendicularTouchSlop = exceededTouchSlopY; + pos = x; + touchDown = mTouchDownX; + offset = pos - mTrackRect.left; + trackSize = mTrackRect.width(); + } + // Do not start scrubbing when dragging in the perpendicular direction if we + // haven't already started quickscrub + if (!mDraggingActive && !mQuickScrubActive && exceededPerpendicularTouchSlop) { + mHandler.removeCallbacksAndMessages(null); + return false; + } + if (!mDragPositive) { + offset -= mIsVertical ? mTrackRect.height() : mTrackRect.width(); + } + + // Control the button movement + if (!mDraggingActive && exceededTouchSlop) { + boolean allowDrag = !mDragPositive + ? offset < 0 && pos < touchDown : offset >= 0 && pos > touchDown; + if (allowDrag) { + mDownOffset = offset; + homeButton.setClickable(false); + mDraggingActive = true; + } + } + if (mDraggingActive && (mDragPositive && offset >= 0 + || !mDragPositive && offset <= 0)) { + float scrubFraction = + Utilities.clamp(Math.abs(offset) * 1f / trackSize, 0, 1); + mTranslation = !mDragPositive + ? Utilities.clamp(offset - mDownOffset, -trackSize, 0) + : Utilities.clamp(offset - mDownOffset, 0, trackSize); + if (mQuickScrubActive) { + try { + overviewProxy.onQuickScrubProgress(scrubFraction); + if (DEBUG_OVERVIEW_PROXY) { + Log.d(TAG_OPS, "Quick Scrub Progress:" + scrubFraction); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to send progress of quick scrub.", e); + } + } else { + mTranslation /= SWITCH_STICKINESS; + } + if (mIsVertical) { + homeButton.getCurrentView().setTranslationY(mTranslation); + } else { + homeButton.getCurrentView().setTranslationX(mTranslation); + } + } + } + break; + } + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + endQuickScrub(); + break; + } + return mDraggingActive || mQuickScrubActive; + } + + @Override + public void onDraw(Canvas canvas) { + int color = (int) mTrackColorEvaluator.evaluate(mDarkIntensity, mLightTrackColor, + mDarkTrackColor); + mTrackPaint.setColor(color); + mTrackPaint.setAlpha((int) (mTrackPaint.getAlpha() * mTrackAlpha)); + canvas.drawRect(mTrackRect, mTrackPaint); + } + + @Override + public void onLayout(boolean changed, int left, int top, int right, int bottom) { + final int width = right - left; + final int height = bottom - top; + final int x1, x2, y1, y2; + if (mIsVertical) { + x1 = (width - mTrackThickness) / 2; + x2 = x1 + mTrackThickness; + y1 = mDragPositive ? height / 2 : mTrackPadding; + y2 = y1 + height / 2 - mTrackPadding; + } else { + y1 = (height - mTrackThickness) / 2; + y2 = y1 + mTrackThickness; + x1 = mDragPositive ? width / 2 : mTrackPadding; + x2 = x1 + width / 2 - mTrackPadding; + } + mTrackRect.set(x1, y1, x2, y2); + + // Get the touch rect of the home button location + View homeView = mNavigationBarView.getHomeButton().getCurrentView(); + if (homeView != null) { + int[] globalHomePos = homeView.getLocationOnScreen(); + int[] globalNavBarPos = mNavigationBarView.getLocationOnScreen(); + int homeX = globalHomePos[0] - globalNavBarPos[0]; + int homeY = globalHomePos[1] - globalNavBarPos[1]; + mHomeButtonRect.set(homeX, homeY, homeX + homeView.getMeasuredWidth(), + homeY + homeView.getMeasuredHeight()); + } + } + + @Override + public void onDarkIntensityChange(float intensity) { + mDarkIntensity = intensity; + mNavigationBarView.invalidate(); + } + + @Override + public void setBarState(boolean isVertical, boolean isRTL) { + mIsVertical = isVertical; + mIsRTL = isRTL; + try { + int navbarPos = WindowManagerGlobal.getWindowManagerService().getNavBarPosition(); + mDragPositive = navbarPos == NAV_BAR_LEFT || navbarPos == NAV_BAR_BOTTOM; + if (isRTL) { + mDragPositive = !mDragPositive; + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to get nav bar position.", e); + } + } + + boolean isQuickScrubEnabled() { + return SystemProperties.getBoolean("persist.quickstep.scrub.enabled", false); + } + + private void startQuickScrub() { + if (!mQuickScrubActive) { + mQuickScrubActive = true; + mLightTrackColor = mContext.getColor(R.color.quick_step_track_background_light); + mDarkTrackColor = mContext.getColor(R.color.quick_step_track_background_dark); + mTrackAnimator.setFloatValues(0, 1); + mTrackAnimator.start(); + try { + mOverviewEventSender.getProxy().onQuickScrubStart(); + if (DEBUG_OVERVIEW_PROXY) { + Log.d(TAG_OPS, "Quick Scrub Start"); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to send start of quick scrub.", e); + } + } + } + + private void endQuickScrub() { + mHandler.removeCallbacks(mLongPressRunnable); + if (mDraggingActive || mQuickScrubActive) { + mButtonAnimator.setIntValues((int) mTranslation, 0); + mTrackAnimator.setFloatValues(mTrackAlpha, 0); + mQuickScrubEndAnimator.start(); + try { + mOverviewEventSender.getProxy().onQuickScrubEnd(); + if (DEBUG_OVERVIEW_PROXY) { + Log.d(TAG_OPS, "Quick Scrub End"); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to send end of quick scrub.", e); + } + } + mDraggingActive = false; + } + + public void cancelQuickSwitch() { + mAllowQuickSwitch = false; + mHandler.removeCallbacks(mLongPressRunnable); + } + + private int getDimensionPixelSize(Context context, @DimenRes int resId) { + return context.getResources().getDimensionPixelSize(resId); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java new file mode 100644 index 000000000000..0d07ad9cdf2e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java @@ -0,0 +1,75 @@ +/* + * 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.phone; + +import android.content.Context; +import android.os.SystemClock; +import android.util.Slog; +import android.view.WindowManager; +import android.widget.Toast; + +import com.android.systemui.R; +import com.android.systemui.SysUIToast; + +/** + * Helper to manage showing/hiding a image to notify them that they are entering or exiting screen + * pinning mode. All exposed methods should be called from a handler thread. + */ +public class ScreenPinningNotify { + private static final String TAG = "ScreenPinningNotify"; + private static final long SHOW_TOAST_MINIMUM_INTERVAL = 1000; + + private final Context mContext; + private Toast mLastToast; + private long mLastShowToastTime; + + public ScreenPinningNotify(Context context) { + mContext = context; + } + + /** Show "Screen pinned" toast. */ + void showPinningStartToast() { + makeAllUserToastAndShow(R.string.screen_pinning_start); + } + + /** Show "Screen unpinned" toast. */ + void showPinningExitToast() { + makeAllUserToastAndShow(R.string.screen_pinning_exit); + } + + /** Show a toast that describes the gesture the user should use to escape pinned mode. */ + void showEscapeToast(boolean isRecentsButtonVisible) { + long showToastTime = SystemClock.elapsedRealtime(); + if ((showToastTime - mLastShowToastTime) < SHOW_TOAST_MINIMUM_INTERVAL) { + Slog.i(TAG, "Ignore toast since it is requested in very short interval."); + return; + } + if (mLastToast != null) { + mLastToast.cancel(); + } + mLastToast = makeAllUserToastAndShow(isRecentsButtonVisible + ? R.string.screen_pinning_toast + : R.string.screen_pinning_toast_recents_invisible); + mLastShowToastTime = showToastTime; + } + + private Toast makeAllUserToastAndShow(int resId) { + Toast toast = SysUIToast.makeText(mContext, resId, Toast.LENGTH_LONG); + toast.show(); + return toast; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 702afa3a38b1..94ebc1bac2b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -20,13 +20,17 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; +import android.app.AlarmManager; import android.app.WallpaperManager; import android.content.Context; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Handler; import android.os.Trace; +import android.util.Log; import android.util.MathUtils; +import android.view.Choreographer; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -34,12 +38,14 @@ import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener; import com.android.internal.graphics.ColorUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dependency; +import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.statusbar.ExpandableNotificationRow; @@ -47,7 +53,11 @@ import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.stack.ViewState; +import com.android.systemui.util.AlarmTimeout; +import com.android.systemui.util.wakelock.DelayedWakeLock; +import com.android.systemui.util.wakelock.WakeLock; +import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.function.Consumer; @@ -56,33 +66,67 @@ import java.util.function.Consumer; * security method gets shown). */ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, - OnHeadsUpChangedListener, OnColorsChangedListener { + OnHeadsUpChangedListener, OnColorsChangedListener, Dumpable { + + private static final String TAG = "ScrimController"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + public static final long ANIMATION_DURATION = 220; public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR = new PathInterpolator(0f, 0, 0.7f, 1f); public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED = new PathInterpolator(0.3f, 0f, 0.8f, 1f); - // Default alpha value for most scrims, if unsure use this constant + + /** + * When both scrims have 0 alpha. + */ + public static final int VISIBILITY_FULLY_TRANSPARENT = 0; + /** + * When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.) + */ + public static final int VISIBILITY_SEMI_TRANSPARENT = 1; + /** + * When at least 1 scrim is fully opaque (alpha set to 1.) + */ + public static final int VISIBILITY_FULLY_OPAQUE = 2; + /** + * Default alpha value for most scrims. + */ public static final float GRADIENT_SCRIM_ALPHA = 0.45f; - // A scrim varies its opacity based on a busyness factor, for example - // how many notifications are currently visible. + /** + * A scrim varies its opacity based on a busyness factor, for example + * how many notifications are currently visible. + */ public static final float GRADIENT_SCRIM_ALPHA_BUSY = 0.70f; + /** + * The most common scrim, the one under the keyguard. + */ protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = GRADIENT_SCRIM_ALPHA; + /** + * We fade out the bottom scrim when the bouncer is visible. + */ protected static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f; - private static final float SCRIM_IN_FRONT_ALPHA = GRADIENT_SCRIM_ALPHA_BUSY; - private static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY; - private static final int TAG_KEY_ANIM = R.id.scrim; + /** + * Opacity of the scrim behind the bouncer (the one doing actual background protection.) + */ + protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY; + + static final int TAG_KEY_ANIM = R.id.scrim; private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target; private static final int TAG_START_ALPHA = R.id.scrim_alpha_start; private static final int TAG_END_ALPHA = R.id.scrim_alpha_end; private static final float NOT_INITIALIZED = -1; - private final LightBarController mLightBarController; + private ScrimState mState = ScrimState.UNINITIALIZED; + private final Context mContext; protected final ScrimView mScrimBehind; protected final ScrimView mScrimInFront; - private final UnlockMethodCache mUnlockMethodCache; private final View mHeadsUpScrim; + private final LightBarController mLightBarController; + private final UnlockMethodCache mUnlockMethodCache; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final DozeParameters mDozeParameters; + private final AlarmTimeout mTimeTicker; private final SysuiColorExtractor mColorExtractor; private GradientColors mLockColors; @@ -94,61 +138,63 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD; protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING; - protected boolean mKeyguardShowing; - private float mFraction; + // Assuming the shade is expanded during initialization + private float mExpansionFraction = 1f; private boolean mDarkenWhileDragging; - protected boolean mBouncerShowing; - protected boolean mBouncerIsKeyguard = false; - private boolean mWakeAndUnlocking; protected boolean mAnimateChange; private boolean mUpdatePending; private boolean mTracking; private boolean mAnimateKeyguardFadingOut; - protected long mDurationOverride = -1; + protected long mAnimationDuration = -1; private long mAnimationDelay; private Runnable mOnAnimationFinished; private boolean mDeferFinishedListener; private final Interpolator mInterpolator = new DecelerateInterpolator(); - private boolean mDozing; - private float mDozeInFrontAlpha; - private float mDozeBehindAlpha; private float mCurrentInFrontAlpha = NOT_INITIALIZED; private float mCurrentBehindAlpha = NOT_INITIALIZED; - private float mCurrentHeadsUpAlpha = NOT_INITIALIZED; + private int mCurrentInFrontTint; + private int mCurrentBehindTint; + private boolean mWallpaperVisibilityTimedOut; private int mPinnedHeadsUpCount; private float mTopHeadsUpDragAmount; private View mDraggedHeadsUpView; - private boolean mForceHideScrims; - private boolean mSkipFirstFrame; - private boolean mDontAnimateBouncerChanges; private boolean mKeyguardFadingOutInProgress; - private boolean mAnimatingDozeUnlock; private ValueAnimator mKeyguardFadeoutAnimation; - /** Wake up from AOD transition is starting; need fully opaque front scrim */ - private boolean mWakingUpFromAodStarting; - /** Wake up from AOD transition is in progress; need black tint */ - private boolean mWakingUpFromAodInProgress; - /** Wake up from AOD transition is animating; need to reset when animation finishes */ - private boolean mWakingUpFromAodAnimationRunning; - private boolean mScrimsVisble; - private final Consumer<Boolean> mScrimVisibleListener; + private int mScrimsVisibility; + private final Consumer<Integer> mScrimVisibleListener; + private boolean mBlankScreen; + private boolean mScreenBlankingCallbackCalled; + private Callback mCallback; + private boolean mWallpaperSupportsAmbientMode; + private boolean mScreenOn; + + // Scrim blanking callbacks + private Choreographer.FrameCallback mPendingFrameCallback; + private Runnable mBlankingTransitionRunnable; + + private final WakeLock mWakeLock; + private boolean mWakeLockHeld; public ScrimController(LightBarController lightBarController, ScrimView scrimBehind, - ScrimView scrimInFront, View headsUpScrim, - Consumer<Boolean> scrimVisibleListener) { + ScrimView scrimInFront, View headsUpScrim, Consumer<Integer> scrimVisibleListener, + DozeParameters dozeParameters, AlarmManager alarmManager) { mScrimBehind = scrimBehind; mScrimInFront = scrimInFront; mHeadsUpScrim = headsUpScrim; mScrimVisibleListener = scrimVisibleListener; - final Context context = scrimBehind.getContext(); - mUnlockMethodCache = UnlockMethodCache.getInstance(context); - mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context); + mContext = scrimBehind.getContext(); + mUnlockMethodCache = UnlockMethodCache.getInstance(mContext); + mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); mLightBarController = lightBarController; - mScrimBehindAlphaResValue = context.getResources().getFloat(R.dimen.scrim_behind_alpha); + mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha); + mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout, + "hide_aod_wallpaper", new Handler()); + mWakeLock = createWakeLock(); // Scrim alpha is initially set to the value on the resource but might be changed // to make sure that text on top of it is legible. mScrimBehindAlpha = mScrimBehindAlphaResValue; + mDozeParameters = dozeParameters; mColorExtractor = Dependency.get(SysuiColorExtractor.class); mColorExtractor.addOnColorsChangedListener(this); @@ -158,159 +204,215 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, ColorExtractor.TYPE_DARK, true /* ignoreVisibility */); mNeedsDrawableColorUpdate = true; + final ScrimState[] states = ScrimState.values(); + for (int i = 0; i < states.length; i++) { + states[i].init(mScrimInFront, mScrimBehind, mDozeParameters); + states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard); + } + mState = ScrimState.UNINITIALIZED; + updateHeadsUpScrim(false); updateScrims(); } - public void setKeyguardShowing(boolean showing) { - mKeyguardShowing = showing; - - // Showing/hiding the keyguard means that scrim colors have to be switched - mNeedsDrawableColorUpdate = true; - scheduleUpdate(); - } - - protected void setScrimBehindValues(float scrimBehindAlphaKeyguard, - float scrimBehindAlphaUnlocking) { - mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; - mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking; - scheduleUpdate(); - } - - public void onTrackingStarted() { - mTracking = true; - mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer(); + public void transitionTo(ScrimState state) { + transitionTo(state, null); } - public void onExpandingFinished() { - mTracking = false; - } - - public void setPanelExpansion(float fraction) { - if (mFraction != fraction) { - mFraction = fraction; - scheduleUpdate(); - if (mPinnedHeadsUpCount != 0) { - updateHeadsUpScrim(false); - } - if (mKeyguardFadeoutAnimation != null && mTracking) { - mKeyguardFadeoutAnimation.cancel(); + public void transitionTo(ScrimState state, Callback callback) { + if (state == mState) { + // Call the callback anyway, unless it's already enqueued + if (callback != null && mCallback != callback) { + callback.onFinished(); } + return; + } else if (DEBUG) { + Log.d(TAG, "State changed to: " + state); } - } - public void setBouncerShowing(boolean showing) { - mBouncerShowing = showing; - mAnimateChange = !mTracking && !mDontAnimateBouncerChanges && !mKeyguardFadingOutInProgress; - scheduleUpdate(); - } + if (state == ScrimState.UNINITIALIZED) { + throw new IllegalArgumentException("Cannot change to UNINITIALIZED."); + } - /** Prepares the wakeUpFromAod animation (while turning on screen); Forces black scrims. */ - public void prepareWakeUpFromAod() { - if (mWakingUpFromAodInProgress) { - return; + final ScrimState oldState = mState; + mState = state; + Trace.traceCounter(Trace.TRACE_TAG_APP, "scrim_state", mState.getIndex()); + + if (mCallback != null) { + mCallback.onCancelled(); } - mWakingUpFromAodInProgress = true; - mWakingUpFromAodStarting = true; - mAnimateChange = false; - scheduleUpdate(); - onPreDraw(); - } + mCallback = callback; - /** Starts the wakeUpFromAod animation (once screen is on); animate to transparent scrims. */ - public void wakeUpFromAod() { - if (mWakeAndUnlocking || mAnimateKeyguardFadingOut) { - // Wake and unlocking has a separate transition that must not be interfered with. - mWakingUpFromAodStarting = false; - mWakingUpFromAodInProgress = false; - return; + state.prepare(oldState); + mScreenBlankingCallbackCalled = false; + mAnimationDelay = 0; + mBlankScreen = state.getBlanksScreen(); + mAnimateChange = state.getAnimateChange(); + mAnimationDuration = state.getAnimationDuration(); + mCurrentInFrontTint = state.getFrontTint(); + mCurrentBehindTint = state.getBehindTint(); + mCurrentInFrontAlpha = state.getFrontAlpha(); + mCurrentBehindAlpha = state.getBehindAlpha(); + applyExpansionToAlpha(); + + // Cancel blanking transitions that were pending before we requested a new state + if (mPendingFrameCallback != null) { + Choreographer.getInstance().removeFrameCallback(mPendingFrameCallback); + mPendingFrameCallback = null; } - if (mWakingUpFromAodStarting) { - mWakingUpFromAodInProgress = true; - mWakingUpFromAodStarting = false; - mAnimateChange = true; - scheduleUpdate(); + if (getHandler().hasCallbacks(mBlankingTransitionRunnable)) { + getHandler().removeCallbacks(mBlankingTransitionRunnable); + mBlankingTransitionRunnable = null; } - } - public void setWakeAndUnlocking() { - mWakeAndUnlocking = true; - mAnimatingDozeUnlock = true; - mWakingUpFromAodStarting = false; - mWakingUpFromAodInProgress = false; - scheduleUpdate(); - } + // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary + // to do the same when you're just showing the brightness mirror. + mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR; - public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished, - boolean skipFirstFrame) { - mWakeAndUnlocking = false; - mAnimateKeyguardFadingOut = true; - mDurationOverride = duration; - mAnimationDelay = delay; - mAnimateChange = true; - mSkipFirstFrame = skipFirstFrame; - mOnAnimationFinished = onAnimationFinished; + if (mKeyguardFadeoutAnimation != null) { + mKeyguardFadeoutAnimation.cancel(); + } - if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) { - scheduleUpdate(); + // The device might sleep if it's entering AOD, we need to make sure that + // the animation plays properly until the last frame. + // It's important to avoid holding the wakelock unless necessary because + // WakeLock#aqcuire will trigger an IPC and will cause jank. + if (mState == ScrimState.AOD) { + holdWakeLock(); + } - // No need to wait for the next frame to be drawn for this case - onPreDraw will execute - // the changes we just scheduled. - onPreDraw(); + // AOD wallpapers should fade away after a while + if (mWallpaperSupportsAmbientMode && mDozeParameters.getAlwaysOn() + && (mState == ScrimState.AOD || mState == ScrimState.PULSING)) { + if (!mWallpaperVisibilityTimedOut) { + mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(), + AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); + } } else { + mTimeTicker.cancel(); + mWallpaperVisibilityTimedOut = false; + } + if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) { // In case the user isn't unlocked, make sure to delay a bit because the system is hosed - // with too many things in this case, in order to not skip the initial frames. + // with too many things at this case, in order to not skip the initial frames. mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16); + mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY; + } else if (!mDozeParameters.getAlwaysOn() && oldState == ScrimState.AOD) { + // Execute first frame immediately when display was completely off. + // Scheduling a frame isn't enough because the system may aggressively enter doze, + // delaying callbacks or never triggering them until the power button is pressed. + onPreDraw(); + } else { + scheduleUpdate(); } } - public void abortKeyguardFadingOut() { - if (mAnimateKeyguardFadingOut) { - endAnimateKeyguardFadingOut(true /* force */); - } + public ScrimState getState() { + return mState; } - public void animateKeyguardUnoccluding(long duration) { - mAnimateChange = false; - setScrimBehindAlpha(0f); - mAnimateChange = true; + protected void setScrimBehindValues(float scrimBehindAlphaKeyguard, + float scrimBehindAlphaUnlocking) { + mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; + mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking; + ScrimState[] states = ScrimState.values(); + for (int i = 0; i < states.length; i++) { + states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard); + } scheduleUpdate(); - mDurationOverride = duration; } - public void animateGoingToFullShade(long delay, long duration) { - mDurationOverride = duration; - mAnimationDelay = delay; + public void onTrackingStarted() { + mTracking = true; + mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer(); + } + + public void onExpandingFinished() { + mTracking = false; + } + + @VisibleForTesting + protected void onHideWallpaperTimeout() { + if (mState != ScrimState.AOD && mState != ScrimState.PULSING) { + return; + } + + holdWakeLock(); + mWallpaperVisibilityTimedOut = true; mAnimateChange = true; + mAnimationDuration = mDozeParameters.getWallpaperFadeOutDuration(); scheduleUpdate(); } - public void setDozing(boolean dozing) { - if (mDozing != dozing) { - mDozing = dozing; - scheduleUpdate(); + private void holdWakeLock() { + if (!mWakeLockHeld) { + if (mWakeLock != null) { + mWakeLockHeld = true; + mWakeLock.acquire(); + } else { + Log.w(TAG, "Cannot hold wake lock, it has not been set yet"); + } } } - public void setDozeInFrontAlpha(float alpha) { - mDozeInFrontAlpha = alpha; - updateScrimColor(mScrimInFront); - } + /** + * Current state of the shade expansion when pulling it from the top. + * This value is 1 when on top of the keyguard and goes to 0 as the user drags up. + * + * The expansion fraction is tied to the scrim opacity. + * + * @param fraction From 0 to 1 where 0 means collapse and 1 expanded. + */ + public void setPanelExpansion(float fraction) { + if (mExpansionFraction != fraction) { + mExpansionFraction = fraction; - public void setDozeBehindAlpha(float alpha) { - mDozeBehindAlpha = alpha; - updateScrimColor(mScrimBehind); - } + if (!(mState == ScrimState.UNLOCKED || mState == ScrimState.KEYGUARD)) { + return; + } - public float getDozeBehindAlpha() { - return mDozeBehindAlpha; + applyExpansionToAlpha(); + + if (mUpdatePending) { + return; + } + + if (mPinnedHeadsUpCount != 0) { + updateHeadsUpScrim(false); + } + updateScrim(false /* animate */, mScrimInFront, mCurrentInFrontAlpha); + updateScrim(false /* animate */, mScrimBehind, mCurrentBehindAlpha); + } } - public float getDozeInFrontAlpha() { - return mDozeInFrontAlpha; + private void applyExpansionToAlpha() { + if (mState == ScrimState.UNLOCKED) { + // Darken scrim as you pull down the shade when unlocked + float behindFraction = getInterpolatedFraction(); + behindFraction = (float) Math.pow(behindFraction, 0.8f); + mCurrentBehindAlpha = behindFraction * mScrimBehindAlphaKeyguard; + mCurrentInFrontAlpha = 0; + } else if (mState == ScrimState.KEYGUARD) { + // Either darken of make the scrim transparent when you + // pull down the shade + float interpolatedFract = getInterpolatedFraction(); + if (mDarkenWhileDragging) { + mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking, + mScrimBehindAlphaKeyguard, interpolatedFract); + mCurrentInFrontAlpha = (1f - interpolatedFract) * SCRIM_IN_FRONT_ALPHA_LOCKED; + } else { + mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, mScrimBehindAlphaKeyguard, + interpolatedFract); + mCurrentInFrontAlpha = 0; + } + } } + /** + * Keyguard and shade scrim opacity varies according to how many notifications are visible. + * @param notificationCount Number of visible notifications. + */ public void setNotificationCount(int notificationCount) { final float maxNotificationDensity = 3; float notificationDensity = Math.min(notificationCount / maxNotificationDensity, 1f); @@ -319,15 +421,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, notificationDensity); if (mScrimBehindAlphaKeyguard != newAlpha) { mScrimBehindAlphaKeyguard = newAlpha; - mAnimateChange = true; - scheduleUpdate(); - } - } - private float getScrimInFrontAlpha() { - return mKeyguardUpdateMonitor.needsSlowUnlockTransition() - ? SCRIM_IN_FRONT_ALPHA_LOCKED - : SCRIM_IN_FRONT_ALPHA; + if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) { + scheduleUpdate(); + } + } } /** @@ -352,7 +450,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, if (mNeedsDrawableColorUpdate) { mNeedsDrawableColorUpdate = false; final GradientColors currentScrimColors; - if (mKeyguardShowing) { + if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) { // Always animate color changes if we're seeing the keyguard mScrimInFront.setColors(mLockColors, true /* animated */); mScrimBehind.setColors(mLockColors, true /* animated */); @@ -375,77 +473,45 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mLightBarController.setScrimColor(mScrimInFront.getColors()); } - if (mAnimateKeyguardFadingOut || mForceHideScrims) { - setScrimInFrontAlpha(0f); - setScrimBehindAlpha(0f); - } else if (mWakeAndUnlocking) { - // During wake and unlock, we first hide everything behind a black scrim, which then - // gets faded out from animateKeyguardFadingOut. This must never be animated. - mAnimateChange = false; - if (mDozing) { - setScrimInFrontAlpha(0f); - setScrimBehindAlpha(1f); - } else { - setScrimInFrontAlpha(1f); - setScrimBehindAlpha(0f); - } - } else if (!mKeyguardShowing && !mBouncerShowing && !mWakingUpFromAodStarting) { - updateScrimNormal(); - setScrimInFrontAlpha(0); - } else { - updateScrimKeyguard(); + // We want to override the back scrim opacity for AOD and PULSING + // when it's time to fade the wallpaper away. + boolean overrideBackScrimAlpha = (mState == ScrimState.PULSING || mState == ScrimState.AOD) + && mWallpaperVisibilityTimedOut; + if (overrideBackScrimAlpha) { + mCurrentBehindAlpha = 1; } - mAnimateChange = false; + + setScrimInFrontAlpha(mCurrentInFrontAlpha); + setScrimBehindAlpha(mCurrentBehindAlpha); + dispatchScrimsVisible(); } private void dispatchScrimsVisible() { - boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0; - - if (mScrimsVisble != scrimsVisible) { - mScrimsVisble = scrimsVisible; - - mScrimVisibleListener.accept(scrimsVisible); + final int currentScrimVisibility; + if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) { + currentScrimVisibility = VISIBILITY_FULLY_OPAQUE; + } else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) { + currentScrimVisibility = VISIBILITY_FULLY_TRANSPARENT; + } else { + currentScrimVisibility = VISIBILITY_SEMI_TRANSPARENT; } - } - private void updateScrimKeyguard() { - if (mTracking && mDarkenWhileDragging) { - float behindFraction = Math.max(0, Math.min(mFraction, 1)); - float fraction = 1 - behindFraction; - fraction = (float) Math.pow(fraction, 0.8f); - behindFraction = (float) Math.pow(behindFraction, 0.8f); - setScrimInFrontAlpha(fraction * getScrimInFrontAlpha()); - setScrimBehindAlpha(behindFraction * mScrimBehindAlphaKeyguard); - } else if (mBouncerShowing && !mBouncerIsKeyguard) { - setScrimInFrontAlpha(getScrimInFrontAlpha()); - updateScrimNormal(); - } else if (mBouncerShowing) { - setScrimInFrontAlpha(0f); - setScrimBehindAlpha(mScrimBehindAlpha); - } else { - float fraction = Math.max(0, Math.min(mFraction, 1)); - if (mWakingUpFromAodStarting) { - setScrimInFrontAlpha(1f); - } else { - setScrimInFrontAlpha(0f); - } - setScrimBehindAlpha(fraction - * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking) - + mScrimBehindAlphaUnlocking); + if (mScrimsVisibility != currentScrimVisibility) { + mScrimsVisibility = currentScrimVisibility; + mScrimVisibleListener.accept(currentScrimVisibility); } } - private void updateScrimNormal() { - float frac = mFraction; + private float getInterpolatedFraction() { + float frac = mExpansionFraction; // let's start this 20% of the way down the screen frac = frac * 1.2f - 0.2f; if (frac <= 0) { - setScrimBehindAlpha(0); + return 0; } else { // woo, special effects - final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f)))); - setScrimBehindAlpha(k * mScrimBehindAlpha); + return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f)))); } } @@ -455,102 +521,76 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private void setScrimInFrontAlpha(float alpha) { setScrimAlpha(mScrimInFront, alpha); - if (alpha == 0f) { - mScrimInFront.setClickable(false); - } else { - // Eat touch events (unless dozing). - mScrimInFront.setClickable(!mDozing); - } } private void setScrimAlpha(View scrim, float alpha) { - updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim)); - } - - protected float getDozeAlpha(View scrim) { - return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha; - } - - protected float getCurrentScrimAlpha(View scrim) { - return scrim == mScrimBehind ? mCurrentBehindAlpha - : scrim == mScrimInFront ? mCurrentInFrontAlpha - : mCurrentHeadsUpAlpha; - } - - private void setCurrentScrimAlpha(View scrim, float alpha) { - if (scrim == mScrimBehind) { - mCurrentBehindAlpha = alpha; - mLightBarController.setScrimAlpha(mCurrentBehindAlpha); - } else if (scrim == mScrimInFront) { - mCurrentInFrontAlpha = alpha; + if (alpha == 0f) { + scrim.setClickable(false); } else { - alpha = Math.max(0.0f, Math.min(1.0f, alpha)); - mCurrentHeadsUpAlpha = alpha; + // Eat touch events (unless dozing). + scrim.setClickable(!(mState == ScrimState.AOD)); } + updateScrim(mAnimateChange, scrim, alpha); } - private void updateScrimColor(View scrim) { - float alpha1 = getCurrentScrimAlpha(scrim); + private void updateScrimColor(View scrim, float alpha, int tint) { + alpha = Math.max(0, Math.min(1.0f, alpha)); if (scrim instanceof ScrimView) { ScrimView scrimView = (ScrimView) scrim; - float dozeAlpha = getDozeAlpha(scrim); - float alpha = 1 - (1 - alpha1) * (1 - dozeAlpha); - alpha = Math.max(0, Math.min(1.0f, alpha)); - scrimView.setViewAlpha(alpha); Trace.traceCounter(Trace.TRACE_TAG_APP, scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha", (int) (alpha * 255)); - int dozeTint = Color.TRANSPARENT; - - boolean dozing = mAnimatingDozeUnlock || mDozing; - boolean frontScrimDozing = mWakingUpFromAodInProgress; - if (dozing || frontScrimDozing && scrim == mScrimInFront) { - dozeTint = Color.BLACK; - } Trace.traceCounter(Trace.TRACE_TAG_APP, scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint", - dozeTint == Color.BLACK ? 1 : 0); + Color.alpha(tint)); - scrimView.setTint(dozeTint); + scrimView.setTint(tint); + scrimView.setViewAlpha(alpha); } else { - scrim.setAlpha(alpha1); + scrim.setAlpha(alpha); } dispatchScrimsVisible(); } - private void startScrimAnimation(final View scrim, float target) { - float current = getCurrentScrimAlpha(scrim); - ValueAnimator anim = ValueAnimator.ofFloat(current, target); + private int getCurrentScrimTint(View scrim) { + return scrim == mScrimInFront ? mCurrentInFrontTint : mCurrentBehindTint; + } + + private void startScrimAnimation(final View scrim, float current, float target) { + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() : + Color.TRANSPARENT; anim.addUpdateListener(animation -> { - float alpha = (float) animation.getAnimatedValue(); - setCurrentScrimAlpha(scrim, alpha); - updateScrimColor(scrim); + final float animAmount = (float) animation.getAnimatedValue(); + final int finalScrimTint = scrim == mScrimInFront ? + mCurrentInFrontTint : mCurrentBehindTint; + float alpha = MathUtils.lerp(current, target, animAmount); + int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount); + updateScrimColor(scrim, alpha, tint); dispatchScrimsVisible(); }); anim.setInterpolator(getInterpolator()); anim.setStartDelay(mAnimationDelay); - anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION); + anim.setDuration(mAnimationDuration); anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - if (!mDeferFinishedListener && mOnAnimationFinished != null) { - mOnAnimationFinished.run(); - mOnAnimationFinished = null; - } if (mKeyguardFadingOutInProgress) { mKeyguardFadeoutAnimation = null; mKeyguardFadingOutInProgress = false; - mAnimatingDozeUnlock = false; - } - if (mWakingUpFromAodAnimationRunning && !mDeferFinishedListener) { - mWakingUpFromAodAnimationRunning = false; - mWakingUpFromAodInProgress = false; } + onFinished(); + scrim.setTag(TAG_KEY_ANIM, null); scrim.setTag(TAG_KEY_ANIM_TARGET, null); dispatchScrimsVisible(); + + if (!mDeferFinishedListener && mOnAnimationFinished != null) { + mOnAnimationFinished.run(); + mOnAnimationFinished = null; + } } }); anim.start(); @@ -558,12 +598,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mKeyguardFadingOutInProgress = true; mKeyguardFadeoutAnimation = anim; } - if (mWakingUpFromAodInProgress) { - mWakingUpFromAodAnimationRunning = true; - } - if (mSkipFirstFrame) { - anim.setCurrentPlayTime(16); - } scrim.setTag(TAG_KEY_ANIM, anim); scrim.setTag(TAG_KEY_ANIM_TARGET, target); } @@ -582,19 +616,33 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, public boolean onPreDraw() { mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this); mUpdatePending = false; - if (mDontAnimateBouncerChanges) { - mDontAnimateBouncerChanges = false; + if (mCallback != null) { + mCallback.onStart(); } updateScrims(); - mDurationOverride = -1; - mAnimationDelay = 0; - mSkipFirstFrame = false; // Make sure that we always call the listener even if we didn't start an animation. endAnimateKeyguardFadingOut(false /* force */); return true; } + private void onFinished() { + if (mWakeLockHeld) { + mWakeLock.release(); + mWakeLockHeld = false; + } + if (mCallback != null) { + mCallback.onFinished(); + mCallback = null; + } + // When unlocking with fingerprint, we'll fade the scrims from black to transparent. + // At the end of the animation we need to remove the tint. + if (mState == ScrimState.UNLOCKED) { + mCurrentInFrontTint = Color.TRANSPARENT; + mCurrentBehindTint = Color.TRANSPARENT; + } + } + private void endAnimateKeyguardFadingOut(boolean force) { mAnimateKeyguardFadingOut = false; if (force || (!isAnimating(mScrimInFront) && !isAnimating(mScrimBehind))) { @@ -603,8 +651,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mOnAnimationFinished = null; } mKeyguardFadingOutInProgress = false; - if (!mWakeAndUnlocking || force) - mAnimatingDozeUnlock = false; } } @@ -641,16 +687,19 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } private void updateHeadsUpScrim(boolean animate) { - updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha); + updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha()); } - private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) { - if (mKeyguardFadingOutInProgress && mKeyguardFadeoutAnimation.getCurrentPlayTime() != 0) { - return; - } + @VisibleForTesting + void setOnAnimationFinished(Runnable onAnimationFinished) { + mOnAnimationFinished = onAnimationFinished; + } + + private void updateScrim(boolean animate, View scrim, float alpha) { + final float currentAlpha = scrim instanceof ScrimView ? ((ScrimView) scrim).getViewAlpha() + : scrim.getAlpha(); - ValueAnimator previousAnimator = ViewState.getChildTag(scrim, - TAG_KEY_ANIM); + ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM); float animEndValue = -1; if (previousAnimator != null) { if (animate || alpha == currentAlpha) { @@ -664,9 +713,38 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, animEndValue = ViewState.getChildTag(scrim, TAG_END_ALPHA); } } - if (alpha != currentAlpha && alpha != animEndValue) { + + if (mPendingFrameCallback != null) { + // Display is off and we're waiting. + return; + } else if (mBlankScreen) { + // Need to blank the display before continuing. + blankDisplay(); + return; + } else if (!mScreenBlankingCallbackCalled) { + // Not blanking the screen. Letting the callback know that we're ready + // to replace what was on the screen before. + if (mCallback != null) { + mCallback.onDisplayBlanked(); + mScreenBlankingCallbackCalled = true; + } + } + + // TODO factor mLightBarController out of this class + if (scrim == mScrimBehind) { + mLightBarController.setScrimAlpha(alpha); + } + + final ScrimView scrimView = scrim instanceof ScrimView ? (ScrimView) scrim : null; + final boolean wantsAlphaUpdate = alpha != currentAlpha && alpha != animEndValue; + final boolean wantsTintUpdate = scrimView != null + && scrimView.getTint() != getCurrentScrimTint(scrimView); + + if (wantsAlphaUpdate || wantsTintUpdate) { if (animate) { - startScrimAnimation(scrim, alpha); + final float fromAlpha = scrimView == null ? scrim.getAlpha() + : scrimView.getViewAlpha(); + startScrimAnimation(scrim, fromAlpha, alpha); scrim.setTag(TAG_START_ALPHA, currentAlpha); scrim.setTag(TAG_END_ALPHA, alpha); } else { @@ -685,13 +763,55 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); } else { // update the alpha directly - setCurrentScrimAlpha(scrim, alpha); - updateScrimColor(scrim); + updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim)); + onFinished(); } } + } else { + onFinished(); } } + private void blankDisplay() { + updateScrimColor(mScrimInFront, 1, Color.BLACK); + + // Notify callback that the screen is completely black and we're + // ready to change the display power mode + mPendingFrameCallback = frameTimeNanos -> { + if (mCallback != null) { + mCallback.onDisplayBlanked(); + mScreenBlankingCallbackCalled = true; + } + + mBlankingTransitionRunnable = () -> { + mBlankingTransitionRunnable = null; + mPendingFrameCallback = null; + mBlankScreen = false; + // Try again. + updateScrims(); + }; + + // Setting power states can happen after we push out the frame. Make sure we + // stay fully opaque until the power state request reaches the lower levels. + final int delay = mScreenOn ? 16 : 500; + if (DEBUG) { + Log.d(TAG, "Fading out scrims with delay: " + delay); + } + getHandler().postDelayed(mBlankingTransitionRunnable, delay); + }; + doOnTheNextFrame(mPendingFrameCallback); + } + + @VisibleForTesting + protected void doOnTheNextFrame(Choreographer.FrameCallback callback) { + Choreographer.getInstance().postFrameCallback(callback); + } + + @VisibleForTesting + protected Handler getHandler() { + return Handler.getMain(); + } + /** * Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means * the heads up is in its resting space and 1 means it's fully dragged out. @@ -714,28 +834,18 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } else { alpha = 1.0f - mTopHeadsUpDragAmount; } - float expandFactor = (1.0f - mFraction); + float expandFactor = (1.0f - mExpansionFraction); expandFactor = Math.max(expandFactor, 0.0f); return alpha * expandFactor; } - public void forceHideScrims(boolean hide, boolean animated) { - mForceHideScrims = hide; - mAnimateChange = animated; - scheduleUpdate(); - } - - public void dontAnimateBouncerChangesUntilNextFrame() { - mDontAnimateBouncerChanges = true; - } - public void setExcludedBackgroundArea(Rect area) { mScrimBehind.setExcludedArea(area); } public int getBackgroundColor() { int color = mLockColors.getMainColor(); - return Color.argb((int) (mScrimBehind.getAlpha() * Color.alpha(color)), + return Color.argb((int) (mScrimBehind.getViewAlpha() * Color.alpha(color)), Color.red(color), Color.green(color), Color.blue(color)); } @@ -764,27 +874,68 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } if ((which & WallpaperManager.FLAG_SYSTEM) != 0) { mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, - ColorExtractor.TYPE_DARK, mKeyguardShowing); + ColorExtractor.TYPE_DARK, mState != ScrimState.UNLOCKED); mNeedsDrawableColorUpdate = true; scheduleUpdate(); } } - public void dump(PrintWriter pw) { - pw.println(" ScrimController:"); + @VisibleForTesting + protected WakeLock createWakeLock() { + return new DelayedWakeLock(getHandler(), + WakeLock.createPartial(mContext, "Doze")); + } - pw.print(" frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha()); + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(" ScrimController: "); + pw.print(" state: "); pw.println(mState); + pw.print(" frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha()); pw.print(" alpha="); pw.print(mCurrentInFrontAlpha); - pw.print(" dozeAlpha="); pw.print(mDozeInFrontAlpha); pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint())); - pw.print(" backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha()); + pw.print(" backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha()); pw.print(" alpha="); pw.print(mCurrentBehindAlpha); - pw.print(" dozeAlpha="); pw.print(mDozeBehindAlpha); pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint())); - pw.print(" mBouncerShowing="); pw.println(mBouncerShowing); pw.print(" mTracking="); pw.println(mTracking); - pw.print(" mForceHideScrims="); pw.println(mForceHideScrims); + } + + public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) { + mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode; + ScrimState[] states = ScrimState.values(); + for (int i = 0; i < states.length; i++) { + states[i].setWallpaperSupportsAmbientMode(wallpaperSupportsAmbientMode); + } + } + + /** + * Interrupts blanking transitions once the display notifies that it's already on. + */ + public void onScreenTurnedOn() { + mScreenOn = true; + final Handler handler = getHandler(); + if (handler.hasCallbacks(mBlankingTransitionRunnable)) { + if (DEBUG) { + Log.d(TAG, "Shorter blanking because screen turned on. All good."); + } + handler.removeCallbacks(mBlankingTransitionRunnable); + mBlankingTransitionRunnable.run(); + } + } + + public void onScreenTurnedOff() { + mScreenOn = false; + } + + public interface Callback { + default void onStart() { + } + default void onDisplayBlanked() { + } + default void onFinished() { + } + default void onCancelled() { + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java new file mode 100644 index 000000000000..381e4af31853 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -0,0 +1,234 @@ +/* + * 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.phone; + +import android.graphics.Color; +import android.os.Trace; + +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.statusbar.ScrimView; +import com.android.systemui.statusbar.stack.StackStateAnimator; + +/** + * Possible states of the ScrimController state machine. + */ +public enum ScrimState { + + /** + * Initial state. + */ + UNINITIALIZED(-1), + + /** + * On the lock screen. + */ + KEYGUARD(0) { + + @Override + public void prepare(ScrimState previousState) { + mBlankScreen = false; + if (previousState == ScrimState.AOD) { + mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP; + if (mDisplayRequiresBlanking) { + // DisplayPowerManager will blank the screen, we'll just + // set our scrim to black in this frame to avoid flickering and + // fade it out afterwards. + mBlankScreen = true; + } + } else { + mAnimationDuration = ScrimController.ANIMATION_DURATION; + } + mCurrentBehindAlpha = mScrimBehindAlphaKeyguard; + mCurrentInFrontAlpha = 0; + } + }, + + /** + * Showing password challenge. + */ + BOUNCER(1) { + @Override + public void prepare(ScrimState previousState) { + mCurrentBehindAlpha = ScrimController.SCRIM_BEHIND_ALPHA_UNLOCKING; + mCurrentInFrontAlpha = ScrimController.SCRIM_IN_FRONT_ALPHA_LOCKED; + } + }, + + /** + * Changing screen brightness from quick settings. + */ + BRIGHTNESS_MIRROR(2) { + @Override + public void prepare(ScrimState previousState) { + mCurrentBehindAlpha = 0; + mCurrentInFrontAlpha = 0; + } + }, + + /** + * Always on display or screen off. + */ + AOD(3) { + @Override + public void prepare(ScrimState previousState) { + final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn(); + final boolean wasPulsing = previousState == ScrimState.PULSING; + mBlankScreen = wasPulsing && !mCanControlScreenOff; + mCurrentBehindAlpha = mWallpaperSupportsAmbientMode + && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f; + mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f; + mCurrentInFrontTint = Color.BLACK; + mCurrentBehindTint = Color.BLACK; + // DisplayPowerManager will blank the screen for us, we just need + // to set our state. + mAnimateChange = mCanControlScreenOff; + } + }, + + /** + * When phone wakes up because you received a notification. + */ + PULSING(4) { + @Override + public void prepare(ScrimState previousState) { + mCurrentInFrontAlpha = 0; + mCurrentInFrontTint = Color.BLACK; + mCurrentBehindAlpha = mWallpaperSupportsAmbientMode + && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f; + mCurrentBehindTint = Color.BLACK; + mBlankScreen = mDisplayRequiresBlanking; + } + }, + + /** + * Unlocked on top of an app (launcher or any other activity.) + */ + UNLOCKED(5) { + @Override + public void prepare(ScrimState previousState) { + mCurrentBehindAlpha = 0; + mCurrentInFrontAlpha = 0; + mAnimationDuration = StatusBar.FADE_KEYGUARD_DURATION; + + if (previousState == ScrimState.AOD) { + // Fade from black to transparent when coming directly from AOD + updateScrimColor(mScrimInFront, 1, Color.BLACK); + updateScrimColor(mScrimBehind, 1, Color.BLACK); + // Scrims should still be black at the end of the transition. + mCurrentInFrontTint = Color.BLACK; + mCurrentBehindTint = Color.BLACK; + mBlankScreen = true; + } else { + // Scrims should still be black at the end of the transition. + mCurrentInFrontTint = Color.TRANSPARENT; + mCurrentBehindTint = Color.TRANSPARENT; + mBlankScreen = false; + } + } + }; + + boolean mBlankScreen = false; + long mAnimationDuration = ScrimController.ANIMATION_DURATION; + int mCurrentInFrontTint = Color.TRANSPARENT; + int mCurrentBehindTint = Color.TRANSPARENT; + boolean mAnimateChange = true; + float mCurrentInFrontAlpha; + float mCurrentBehindAlpha; + float mAodFrontScrimAlpha; + float mScrimBehindAlphaKeyguard; + ScrimView mScrimInFront; + ScrimView mScrimBehind; + DozeParameters mDozeParameters; + boolean mDisplayRequiresBlanking; + boolean mCanControlScreenOff; + boolean mWallpaperSupportsAmbientMode; + KeyguardUpdateMonitor mKeyguardUpdateMonitor; + int mIndex; + + ScrimState(int index) { + mIndex = index; + } + + public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) { + mScrimInFront = scrimInFront; + mScrimBehind = scrimBehind; + mDozeParameters = dozeParameters; + mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking(); + mCanControlScreenOff = dozeParameters.getCanControlScreenOffAnimation(); + mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(scrimInFront.getContext()); + } + + public void prepare(ScrimState previousState) { + } + + public int getIndex() { + return mIndex; + } + + public float getFrontAlpha() { + return mCurrentInFrontAlpha; + } + + public float getBehindAlpha() { + return mCurrentBehindAlpha; + } + + public int getFrontTint() { + return mCurrentInFrontTint; + } + + public int getBehindTint() { + return mCurrentBehindTint; + } + + public long getAnimationDuration() { + return mAnimationDuration; + } + + public boolean getBlanksScreen() { + return mBlankScreen; + } + + public void updateScrimColor(ScrimView scrim, float alpha, int tint) { + Trace.traceCounter(Trace.TRACE_TAG_APP, + scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha", + (int) (alpha * 255)); + + Trace.traceCounter(Trace.TRACE_TAG_APP, + scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint", + Color.alpha(tint)); + + scrim.setTint(tint); + scrim.setViewAlpha(alpha); + } + + public boolean getAnimateChange() { + return mAnimateChange; + } + + public void setAodFrontScrimAlpha(float aodFrontScrimAlpha) { + mAodFrontScrimAlpha = aodFrontScrimAlpha; + } + + public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) { + mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; + } + + public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) { + mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode; + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java index 6220fcbdf13b..1130b6de2544 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java @@ -32,6 +32,8 @@ import com.android.systemui.Interpolators; public class SettingsButton extends AlphaOptimizedImageButton { + private static final boolean TUNER_ENABLE_AVAILABLE = false; + private static final long LONG_PRESS_LENGTH = 1000; private static final long ACCEL_LENGTH = 750; private static final long FULL_SPEED_LENGTH = 375; @@ -59,7 +61,7 @@ public class SettingsButton extends AlphaOptimizedImageButton { public boolean onTouchEvent(MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: - postDelayed(mLongPressCallback, LONG_PRESS_LENGTH); + if (TUNER_ENABLE_AVAILABLE) postDelayed(mLongPressCallback, LONG_PRESS_LENGTH); break; case MotionEvent.ACTION_UP: if (mUpToSpeed) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java deleted file mode 100644 index 15ef742af02e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java +++ /dev/null @@ -1,517 +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 com.android.systemui.statusbar.phone; - -import android.animation.ArgbEvaluator; -import android.annotation.IntRange; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Path.Direction; -import android.graphics.Path.FillType; -import android.graphics.Path.Op; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.util.LayoutDirection; - -import com.android.settingslib.R; -import com.android.settingslib.Utils; -import com.android.systemui.qs.SlashDrawable; - -public class SignalDrawable extends Drawable { - - private static final String TAG = "SignalDrawable"; - - private static final int NUM_DOTS = 3; - - private static final float VIEWPORT = 24f; - private static final float PAD = 2f / VIEWPORT; - private static final float CUT_OUT = 7.9f / VIEWPORT; - - private static final float DOT_SIZE = 3f / VIEWPORT; - private static final float DOT_PADDING = 1f / VIEWPORT; - private static final float DOT_CUT_WIDTH = (DOT_SIZE * 3) + (DOT_PADDING * 5); - private static final float DOT_CUT_HEIGHT = (DOT_SIZE * 1) + (DOT_PADDING * 1); - - private static final float[] FIT = {2.26f, -3.02f, 1.76f}; - - // All of these are masks to push all of the drawable state into one int for easy callbacks - // and flow through sysui. - private static final int LEVEL_MASK = 0xff; - private static final int NUM_LEVEL_SHIFT = 8; - private static final int NUM_LEVEL_MASK = 0xff << NUM_LEVEL_SHIFT; - private static final int STATE_SHIFT = 16; - private static final int STATE_MASK = 0xff << STATE_SHIFT; - private static final int STATE_NONE = 0; - private static final int STATE_EMPTY = 1; - private static final int STATE_CUT = 2; - private static final int STATE_CARRIER_CHANGE = 3; - private static final int STATE_AIRPLANE = 4; - - private static final long DOT_DELAY = 1000; - - private static float[][] X_PATH = new float[][]{ - {21.9f / VIEWPORT, 17.0f / VIEWPORT}, - {-1.1f / VIEWPORT, -1.1f / VIEWPORT}, - {-1.9f / VIEWPORT, 1.9f / VIEWPORT}, - {-1.9f / VIEWPORT, -1.9f / VIEWPORT}, - {-1.1f / VIEWPORT, 1.1f / VIEWPORT}, - {1.9f / VIEWPORT, 1.9f / VIEWPORT}, - {-1.9f / VIEWPORT, 1.9f / VIEWPORT}, - {1.1f / VIEWPORT, 1.1f / VIEWPORT}, - {1.9f / VIEWPORT, -1.9f / VIEWPORT}, - {1.9f / VIEWPORT, 1.9f / VIEWPORT}, - {1.1f / VIEWPORT, -1.1f / VIEWPORT}, - {-1.9f / VIEWPORT, -1.9f / VIEWPORT}, - }; - - // Rounded corners are achieved by arcing a circle of radius `R` from its tangent points along - // the curve (curve ≡ triangle). On the top and left corners of the triangle, the tangents are - // as follows: - // 1) Along the straight lines (y = 0 and x = width): - // Ps = circleOffset + R - // 2) Along the diagonal line (y = x): - // Pd = √((Ps^2) / 2) - // or (remember: sin(π/4) ≈ 0.7071) - // Pd = (circleOffset + R - 0.7071, height - R - 0.7071) - // Where Pd is the (x,y) coords of the point that intersects the circle at the bottom - // left of the triangle - private static final float RADIUS_RATIO = 0.75f / 17f; - private static final float DIAG_OFFSET_MULTIPLIER = 0.707107f; - // How far the circle defining the corners is inset from the edges - private final float mAppliedCornerInset; - - private static final float INV_TAN = 1f / (float) Math.tan(Math.PI / 8f); - private static final float CUT_WIDTH_DP = 1f / 12f; - - // Where the top and left points of the triangle would be if not for rounding - private final PointF mVirtualTop = new PointF(); - private final PointF mVirtualLeft = new PointF(); - - private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private final Paint mForegroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private final int mDarkModeBackgroundColor; - private final int mDarkModeFillColor; - private final int mLightModeBackgroundColor; - private final int mLightModeFillColor; - private final Path mFullPath = new Path(); - private final Path mForegroundPath = new Path(); - private final Path mXPath = new Path(); - // Cut out when STATE_EMPTY - private final Path mCutPath = new Path(); - // Draws the slash when in airplane mode - private final SlashArtist mSlash = new SlashArtist(); - private final Handler mHandler; - private float mOldDarkIntensity = -1; - private float mNumLevels = 1; - private int mIntrinsicSize; - private int mLevel; - private int mState; - private boolean mVisible; - private boolean mAnimating; - private int mCurrentDot; - - public SignalDrawable(Context context) { - mDarkModeBackgroundColor = - Utils.getDefaultColor(context, R.color.dark_mode_icon_color_dual_tone_background); - mDarkModeFillColor = - Utils.getDefaultColor(context, R.color.dark_mode_icon_color_dual_tone_fill); - mLightModeBackgroundColor = - Utils.getDefaultColor(context, R.color.light_mode_icon_color_dual_tone_background); - mLightModeFillColor = - Utils.getDefaultColor(context, R.color.light_mode_icon_color_dual_tone_fill); - mIntrinsicSize = context.getResources().getDimensionPixelSize(R.dimen.signal_icon_size); - - mHandler = new Handler(); - setDarkIntensity(0); - - mAppliedCornerInset = context.getResources() - .getDimensionPixelSize(R.dimen.stat_sys_mobile_signal_circle_inset); - } - - public void setIntrinsicSize(int size) { - mIntrinsicSize = size; - } - - @Override - public int getIntrinsicWidth() { - return mIntrinsicSize; - } - - @Override - public int getIntrinsicHeight() { - return mIntrinsicSize; - } - - public void setNumLevels(int levels) { - if (levels == mNumLevels) return; - mNumLevels = levels; - invalidateSelf(); - } - - private void setSignalState(int state) { - if (state == mState) return; - mState = state; - updateAnimation(); - invalidateSelf(); - } - - private void updateAnimation() { - boolean shouldAnimate = (mState == STATE_CARRIER_CHANGE) && mVisible; - if (shouldAnimate == mAnimating) return; - mAnimating = shouldAnimate; - if (shouldAnimate) { - mChangeDot.run(); - } else { - mHandler.removeCallbacks(mChangeDot); - } - } - - @Override - protected boolean onLevelChange(int state) { - setNumLevels(getNumLevels(state)); - setSignalState(getState(state)); - int level = getLevel(state); - if (level != mLevel) { - mLevel = level; - invalidateSelf(); - } - return true; - } - - public void setColors(int background, int foreground) { - mPaint.setColor(background); - mForegroundPaint.setColor(foreground); - } - - public void setDarkIntensity(float darkIntensity) { - if (darkIntensity == mOldDarkIntensity) { - return; - } - mPaint.setColor(getBackgroundColor(darkIntensity)); - mForegroundPaint.setColor(getFillColor(darkIntensity)); - mOldDarkIntensity = darkIntensity; - invalidateSelf(); - } - - private int getFillColor(float darkIntensity) { - return getColorForDarkIntensity( - darkIntensity, mLightModeFillColor, mDarkModeFillColor); - } - - private int getBackgroundColor(float darkIntensity) { - return getColorForDarkIntensity( - darkIntensity, mLightModeBackgroundColor, mDarkModeBackgroundColor); - } - - private int getColorForDarkIntensity(float darkIntensity, int lightColor, int darkColor) { - return (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, lightColor, darkColor); - } - - @Override - protected void onBoundsChange(Rect bounds) { - super.onBoundsChange(bounds); - invalidateSelf(); - } - - @Override - public void draw(@NonNull Canvas canvas) { - final float width = getBounds().width(); - final float height = getBounds().height(); - - boolean isRtl = getLayoutDirection() == LayoutDirection.RTL; - if (isRtl) { - canvas.save(); - // Mirror the drawable - canvas.translate(width, 0); - canvas.scale(-1.0f, 1.0f); - } - mFullPath.reset(); - mFullPath.setFillType(FillType.WINDING); - - final float padding = Math.round(PAD * width); - final float cornerRadius = RADIUS_RATIO * height; - // Offset from circle where the hypotenuse meets the circle - final float diagOffset = DIAG_OFFSET_MULTIPLIER * cornerRadius; - - // 1 - Bottom right, above corner - mFullPath.moveTo(width - padding, height - padding - cornerRadius); - // 2 - Line to top right, below corner - mFullPath.lineTo(width - padding, padding + cornerRadius + mAppliedCornerInset); - // 3 - Arc to top right, on hypotenuse - mFullPath.arcTo( - width - padding - (2 * cornerRadius), - padding + mAppliedCornerInset, - width - padding, - padding + mAppliedCornerInset + (2 * cornerRadius), - 0.f, -135.f, false - ); - // 4 - Line to bottom left, on hypotenuse - mFullPath.lineTo(padding + mAppliedCornerInset + cornerRadius - diagOffset, - height - padding - cornerRadius - diagOffset); - // 5 - Arc to bottom left, on leg - mFullPath.arcTo( - padding + mAppliedCornerInset, - height - padding - (2 * cornerRadius), - padding + mAppliedCornerInset + ( 2 * cornerRadius), - height - padding, - -135.f, -135.f, false - ); - // 6 - Line to bottom rght, before corner - mFullPath.lineTo(width - padding - cornerRadius, height - padding); - // 7 - Arc to beginning (bottom right, above corner) - mFullPath.arcTo( - width - padding - (2 * cornerRadius), - height - padding - (2 * cornerRadius), - width - padding, - height - padding, - 90.f, -90.f, false - ); - - if (mState == STATE_CARRIER_CHANGE) { - float cutWidth = (DOT_CUT_WIDTH * width); - float cutHeight = (DOT_CUT_HEIGHT * width); - float dotSize = (DOT_SIZE * height); - float dotPadding = (DOT_PADDING * height); - - mFullPath.moveTo(width - padding, height - padding); - mFullPath.rLineTo(-cutWidth, 0); - mFullPath.rLineTo(0, -cutHeight); - mFullPath.rLineTo(cutWidth, 0); - mFullPath.rLineTo(0, cutHeight); - float dotSpacing = dotPadding * 2 + dotSize; - float x = width - padding - dotSize; - float y = height - padding - dotSize; - mForegroundPath.reset(); - drawDot(mFullPath, mForegroundPath, x, y, dotSize, 2); - drawDot(mFullPath, mForegroundPath, x - dotSpacing, y, dotSize, 1); - drawDot(mFullPath, mForegroundPath, x - dotSpacing * 2, y, dotSize, 0); - } else if (mState == STATE_CUT) { - float cut = (CUT_OUT * width); - mFullPath.moveTo(width - padding, height - padding); - mFullPath.rLineTo(-cut, 0); - mFullPath.rLineTo(0, -cut); - mFullPath.rLineTo(cut, 0); - mFullPath.rLineTo(0, cut); - } - - if (mState == STATE_EMPTY) { - // Where the corners would be if this were a real triangle - mVirtualTop.set( - width - padding, - (padding + cornerRadius + mAppliedCornerInset) - (INV_TAN * cornerRadius)); - mVirtualLeft.set( - (padding + cornerRadius + mAppliedCornerInset) - (INV_TAN * cornerRadius), - height - padding); - - final float cutWidth = CUT_WIDTH_DP * height; - final float cutDiagInset = cutWidth * INV_TAN; - - // Cut out a smaller triangle from the center of mFullPath - mCutPath.reset(); - mCutPath.setFillType(FillType.WINDING); - mCutPath.moveTo(width - padding - cutWidth, height - padding - cutWidth); - mCutPath.lineTo(width - padding - cutWidth, mVirtualTop.y + cutDiagInset); - mCutPath.lineTo(mVirtualLeft.x + cutDiagInset, height - padding - cutWidth); - mCutPath.lineTo(width - padding - cutWidth, height - padding - cutWidth); - - // Draw empty state as only background - mForegroundPath.reset(); - mFullPath.op(mCutPath, Path.Op.DIFFERENCE); - } else if (mState == STATE_AIRPLANE) { - // Airplane mode is slashed, fully drawn background - mForegroundPath.reset(); - mSlash.draw((int) height, (int) width, canvas, mPaint); - } else if (mState != STATE_CARRIER_CHANGE) { - mForegroundPath.reset(); - int sigWidth = Math.round(calcFit(mLevel / (mNumLevels - 1)) * (width - 2 * padding)); - mForegroundPath.addRect(padding, padding, padding + sigWidth, height - padding, - Direction.CW); - mForegroundPath.op(mFullPath, Op.INTERSECT); - } - - canvas.drawPath(mFullPath, mPaint); - canvas.drawPath(mForegroundPath, mForegroundPaint); - if (mState == STATE_CUT) { - mXPath.reset(); - mXPath.moveTo(X_PATH[0][0] * width, X_PATH[0][1] * height); - for (int i = 1; i < X_PATH.length; i++) { - mXPath.rLineTo(X_PATH[i][0] * width, X_PATH[i][1] * height); - } - canvas.drawPath(mXPath, mForegroundPaint); - } - if (isRtl) { - canvas.restore(); - } - } - - private void drawDot(Path fullPath, Path foregroundPath, float x, float y, float dotSize, - int i) { - Path p = (i == mCurrentDot) ? foregroundPath : fullPath; - p.addRect(x, y, x + dotSize, y + dotSize, Direction.CW); - } - - // This is a fit line based on previous values of provided in assets, but if - // you look at the a plot of this actual fit, it makes a lot of sense, what it does - // is compress the areas that are very visually easy to see changes (the middle sections) - // and spread out the sections that are hard to see (each end of the icon). - // The current fit is cubic, but pretty easy to change the way the code is written (just add - // terms to the end of FIT). - private float calcFit(float v) { - float ret = 0; - float t = v; - for (int i = 0; i < FIT.length; i++) { - ret += FIT[i] * t; - t *= v; - } - return ret; - } - - @Override - public int getAlpha() { - return mPaint.getAlpha(); - } - - @Override - public void setAlpha(@IntRange(from = 0, to = 255) int alpha) { - mPaint.setAlpha(alpha); - mForegroundPaint.setAlpha(alpha); - } - - @Override - public void setColorFilter(@Nullable ColorFilter colorFilter) { - mPaint.setColorFilter(colorFilter); - mForegroundPaint.setColorFilter(colorFilter); - } - - @Override - public int getOpacity() { - return 255; - } - - @Override - public boolean setVisible(boolean visible, boolean restart) { - mVisible = visible; - updateAnimation(); - return super.setVisible(visible, restart); - } - - private final Runnable mChangeDot = new Runnable() { - @Override - public void run() { - if (++mCurrentDot == NUM_DOTS) { - mCurrentDot = 0; - } - invalidateSelf(); - mHandler.postDelayed(mChangeDot, DOT_DELAY); - } - }; - - public static int getLevel(int fullState) { - return fullState & LEVEL_MASK; - } - - public static int getState(int fullState) { - return (fullState & STATE_MASK) >> STATE_SHIFT; - } - - public static int getNumLevels(int fullState) { - return (fullState & NUM_LEVEL_MASK) >> NUM_LEVEL_SHIFT; - } - - public static int getState(int level, int numLevels, boolean cutOut) { - return ((cutOut ? STATE_CUT : 0) << STATE_SHIFT) - | (numLevels << NUM_LEVEL_SHIFT) - | level; - } - - public static int getCarrierChangeState(int numLevels) { - return (STATE_CARRIER_CHANGE << STATE_SHIFT) | (numLevels << NUM_LEVEL_SHIFT); - } - - public static int getEmptyState(int numLevels) { - return (STATE_EMPTY << STATE_SHIFT) | (numLevels << NUM_LEVEL_SHIFT); - } - - public static int getAirplaneModeState(int numLevels) { - return (STATE_AIRPLANE << STATE_SHIFT) | (numLevels << NUM_LEVEL_SHIFT); - } - - private final class SlashArtist { - // These values are derived in un-rotated (vertical) orientation - private static final float SLASH_WIDTH = 1.8384776f; - private static final float SLASH_HEIGHT = 22f; - private static final float CENTER_X = 10.65f; - private static final float CENTER_Y = 15.869239f; - private static final float SCALE = 24f; - - // Bottom is derived during animation - private static final float LEFT = (CENTER_X - (SLASH_WIDTH / 2)) / SCALE; - private static final float TOP = (CENTER_Y - (SLASH_HEIGHT / 2)) / SCALE; - private static final float RIGHT = (CENTER_X + (SLASH_WIDTH / 2)) / SCALE; - private static final float BOTTOM = (CENTER_Y + (SLASH_HEIGHT / 2)) / SCALE; - // Draw the slash washington-monument style; rotate to no-u-turn style - private static final float ROTATION = -45f; - - private final Path mPath = new Path(); - private final RectF mSlashRect = new RectF(); - - void draw(int height, int width, @NonNull Canvas canvas, Paint paint) { - Matrix m = new Matrix(); - final float radius = scale(SlashDrawable.CORNER_RADIUS, width); - updateRect( - scale(LEFT, width), - scale(TOP, height), - scale(RIGHT, width), - scale(BOTTOM, height)); - - mPath.reset(); - // Draw the slash vertically - mPath.addRoundRect(mSlashRect, radius, radius, Direction.CW); - m.setRotate(ROTATION, width / 2, height / 2); - mPath.transform(m); - canvas.drawPath(mPath, paint); - - // Rotate back to vertical, and draw the cut-out rect next to this one - m.setRotate(-ROTATION, width / 2, height / 2); - mPath.transform(m); - m.setTranslate(mSlashRect.width(), 0); - mPath.transform(m); - mPath.addRoundRect(mSlashRect, radius, radius, Direction.CW); - m.setRotate(ROTATION, width / 2, height / 2); - mPath.transform(m); - canvas.clipOutPath(mPath); - } - - void updateRect(float left, float top, float right, float bottom) { - mSlashRect.left = left; - mSlashRect.top = top; - mSlashRect.right = right; - mSlashRect.bottom = bottom; - } - - private float scale(float frac, int width) { - return frac * width; - } - } -} 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 42a35c1d409d..3b63d6c39146 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -19,11 +19,15 @@ package com.android.systemui.statusbar.phone; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.windowStateToString; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING; -import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager + .NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF; +import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; @@ -37,18 +41,16 @@ import android.animation.AnimatorListenerAdapter; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; -import android.app.ActivityManager.StackId; import android.app.ActivityOptions; -import android.app.INotificationManager; +import android.app.AlarmManager; import android.app.KeyguardManager; import android.app.Notification; -import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.RemoteInput; import android.app.StatusBarManager; import android.app.TaskStackBuilder; import android.app.WallpaperColors; +import android.app.WallpaperInfo; import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; @@ -60,18 +62,13 @@ import android.content.IntentFilter; import android.content.IntentSender; import android.content.om.IOverlayManager; import android.content.om.OverlayInfo; -import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.Resources; -import android.database.ContentObserver; import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.PointF; import android.graphics.PorterDuff; @@ -82,17 +79,13 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.media.AudioAttributes; import android.media.MediaMetadata; -import android.media.session.MediaController; -import android.media.session.MediaSession; -import android.media.session.MediaSessionManager; -import android.media.session.PlaybackState; import android.metrics.LogMaker; import android.net.Uri; import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; @@ -104,41 +97,32 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.Vibrator; import android.provider.Settings; -import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; import android.text.TextUtils; -import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.util.SparseArray; -import android.util.SparseBooleanArray; import android.view.Display; -import android.view.HapticFeedbackConstants; import android.view.IWindowManager; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.ThreadedRenderer; import android.view.View; -import android.view.ViewAnimationUtils; import android.view.ViewGroup; import android.view.ViewParent; -import android.view.ViewStub; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.view.animation.AccelerateInterpolator; -import android.view.animation.Interpolator; import android.widget.DateTimeView; import android.widget.ImageView; -import android.widget.RemoteViews; import android.widget.TextView; -import android.widget.Toast; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; @@ -146,30 +130,28 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.statusbar.IStatusBarService; -import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.statusbar.StatusBarIcon; -import com.android.internal.util.NotificationMessagingUtil; import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.MessagingGroup; +import com.android.internal.widget.MessagingMessage; import com.android.keyguard.KeyguardHostView.OnDismissAction; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.ActivityStarterDelegate; import com.android.systemui.AutoReinflateContainer; -import com.android.systemui.DejankUtils; import com.android.systemui.DemoMode; import com.android.systemui.Dependency; import com.android.systemui.EventLogTags; -import com.android.systemui.ForegroundServiceController; import com.android.systemui.Interpolators; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.RecentsComponent; -import com.android.systemui.SwipeHelper; import com.android.systemui.SystemUI; import com.android.systemui.SystemUIFactory; import com.android.systemui.UiOffloadThread; import com.android.systemui.assist.AssistManager; +import com.android.systemui.charging.WirelessChargingAnimation; import com.android.systemui.classifier.FalsingLog; import com.android.systemui.classifier.FalsingManager; import com.android.systemui.colorextraction.SysuiColorExtractor; @@ -183,7 +165,6 @@ import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QS; -import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; import com.android.systemui.qs.QSFragment; import com.android.systemui.qs.QSPanel; @@ -200,6 +181,7 @@ import com.android.systemui.stackdivider.WindowManagerProxy; import com.android.systemui.statusbar.ActivatableNotificationView; import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.DismissView; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.EmptyShadeView; @@ -209,18 +191,25 @@ import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationData.Entry; -import com.android.systemui.statusbar.NotificationGuts; +import com.android.systemui.statusbar.NotificationEntryManager; +import com.android.systemui.statusbar.NotificationGutsManager; import com.android.systemui.statusbar.NotificationInfo; +import com.android.systemui.statusbar.NotificationListener; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationLogger; +import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.NotificationPresenter; +import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShelf; -import com.android.systemui.statusbar.NotificationSnooze; +import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.SignalClusterView; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.AboveShelfObserver; -import com.android.systemui.statusbar.notification.InflationException; -import com.android.systemui.statusbar.notification.RowInflaterTask; +import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; @@ -232,22 +221,18 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.PreviewInflater; -import com.android.systemui.statusbar.policy.RemoteInputView; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; -import com.android.systemui.statusbar.stack.NotificationStackScrollLayout - .OnChildLocationsChangedListener; -import com.android.systemui.statusbar.stack.StackStateAnimator; import com.android.systemui.util.NotificationChannels; -import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.volume.VolumeComponent; import java.io.FileDescriptor; @@ -255,48 +240,24 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Set; -import java.util.Stack; public class StatusBar extends SystemUI implements DemoMode, DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener, - OnHeadsUpChangedListener, VisualStabilityManager.Callback, CommandQueue.Callbacks, - ActivatableNotificationView.OnActivatedListener, - ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment, - ExpandableNotificationRow.OnExpandClickListener, InflationCallback, - ColorExtractor.OnColorsChangedListener, ConfigurationListener { + OnHeadsUpChangedListener, CommandQueue.Callbacks, + ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter { public static final boolean MULTIUSER_DEBUG = false; - public static final boolean ENABLE_REMOTE_INPUT = - SystemProperties.getBoolean("debug.enable_remote_input", true); public static final boolean ENABLE_CHILD_NOTIFICATIONS = SystemProperties.getBoolean("debug.child_notifs", true); - public static final boolean FORCE_REMOTE_INPUT_HISTORY = - SystemProperties.getBoolean("debug.force_remoteinput_history", false); - private static boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false; - protected static final int MSG_SHOW_RECENT_APPS = 1019; protected static final int MSG_HIDE_RECENT_APPS = 1020; - protected static final int MSG_TOGGLE_RECENTS_APPS = 1021; protected static final int MSG_PRELOAD_RECENT_APPS = 1022; protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026; protected static final int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027; - protected static final boolean ENABLE_HEADS_UP = true; - protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; - - // Must match constant in Settings. Used to highlight preferences when linking to Settings. - private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; - - private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; - // Should match the values in PhoneWindowManager public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey"; public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; @@ -306,14 +267,11 @@ public class StatusBar extends SystemUI implements DemoMode, "com.android.systemui.statusbar.banner_action_cancel"; private static final String BANNER_ACTION_SETUP = "com.android.systemui.statusbar.banner_action_setup"; - private static final String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION - = "com.android.systemui.statusbar.work_challenge_unlocked_notification_action"; public static final String TAG = "StatusBar"; public static final boolean DEBUG = false; public static final boolean SPEW = false; public static final boolean DUMPTRUCK = true; // extra dumpsys info public static final boolean DEBUG_GESTURES = false; - public static final boolean DEBUG_MEDIA = false; public static final boolean DEBUG_MEDIA_FAKE_ARTWORK = false; public static final boolean DEBUG_CAMERA_LIFT = false; @@ -335,15 +293,12 @@ public class StatusBar extends SystemUI implements DemoMode, // Time after we abort the launch transition. private static final long LAUNCH_TRANSITION_TIMEOUT_MS = 5000; - private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true; + protected static final boolean CLOSE_PANEL_WHEN_EMPTIED = true; private static final int STATUS_OR_NAV_TRANSIENT = View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT; private static final long AUTOHIDE_TIMEOUT_MS = 2250; - /** The minimum delay in ms between reports of notification visibility. */ - private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500; - /** * The delay to reset the hint text when the hint animation is finished running. */ @@ -365,18 +320,6 @@ public class StatusBar extends SystemUI implements DemoMode, /** If true, the lockscreen will show a distinct wallpaper */ private static final boolean ENABLE_LOCKSCREEN_WALLPAPER = true; - /* If true, the device supports freeform window management. - * This affects the status bar UI. */ - private static final boolean FREEFORM_WINDOW_MANAGEMENT; - - /** - * How long to wait before auto-dismissing a notification that was kept for remote input, and - * has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel - * these given that they technically don't exist anymore. We wait a bit in case the app issues - * an update. - */ - private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200; - /** * Never let the alpha become zero for surfaces that draw with SRC - otherwise the RenderNode * won't draw anything and uninitialized memory will show through @@ -387,19 +330,14 @@ public class StatusBar extends SystemUI implements DemoMode, static { boolean onlyCoreApps; - boolean freeformWindowManagement; try { IPackageManager packageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); onlyCoreApps = packageManager.isOnlyCoreApps(); - freeformWindowManagement = packageManager.hasSystemFeature( - PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT, 0); } catch (RemoteException e) { onlyCoreApps = false; - freeformWindowManagement = false; } ONLY_CORE_APPS = onlyCoreApps; - FREEFORM_WINDOW_MANAGEMENT = freeformWindowManagement; } /** @@ -407,20 +345,19 @@ public class StatusBar extends SystemUI implements DemoMode, */ protected int mState; protected boolean mBouncerShowing; - protected boolean mShowLockscreenNotifications; - protected boolean mAllowLockscreenRemoteInput; - PhoneStatusBarPolicy mIconPolicy; + private PhoneStatusBarPolicy mIconPolicy; - VolumeComponent mVolumeComponent; - BrightnessMirrorController mBrightnessMirrorController; + private VolumeComponent mVolumeComponent; + private BrightnessMirrorController mBrightnessMirrorController; + private boolean mBrightnessMirrorVisible; protected FingerprintUnlockController mFingerprintUnlockController; - LightBarController mLightBarController; + private LightBarController mLightBarController; protected LockscreenWallpaper mLockscreenWallpaper; - int mNaturalBarHeight = -1; + private int mNaturalBarHeight = -1; - Point mCurrentDisplaySize = new Point(); + private final Point mCurrentDisplaySize = new Point(); protected StatusBarWindowView mStatusBarWindow; protected PhoneStatusBarView mStatusBarView; @@ -431,33 +368,22 @@ public class StatusBar extends SystemUI implements DemoMode, private boolean mWakeUpComingFromTouch; private PointF mWakeUpTouchLocation; - int mPixelFormat; - Object mQueueLock = new Object(); + private final Object mQueueLock = new Object(); protected StatusBarIconController mIconController; // expanded notifications protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window - View mExpandedContents; - TextView mNotificationPanelDebugText; - - /** - * {@code true} if notifications not part of a group should by default be rendered in their - * expanded state. If {@code false}, then only the first notification will be expanded if - * possible. - */ - private boolean mAlwaysExpandNonGroupedNotification; + private TextView mNotificationPanelDebugText; // settings private QSPanel mQSPanel; // top bar - protected KeyguardStatusBarView mKeyguardStatusBar; - boolean mLeaveOpenOnKeyguardHide; + private KeyguardStatusBarView mKeyguardStatusBar; + private boolean mLeaveOpenOnKeyguardHide; KeyguardIndicationController mKeyguardIndicationController; - // Keyguard is going away soon. - private boolean mKeyguardGoingAway; // Keyguard is actually fading away now. protected boolean mKeyguardFadingAway; protected long mKeyguardFadingAwayDelay; @@ -469,25 +395,31 @@ public class StatusBar extends SystemUI implements DemoMode, private View mReportRejectedTouch; - int mMaxAllowedKeyguardNotifications; + private int mMaxAllowedKeyguardNotifications; - boolean mExpandedVisible; + private boolean mExpandedVisible; - // the tracker view - int mTrackingPosition; // the position of the top of the tracking view. + private final int[] mAbsPos = new int[2]; + private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>(); - // Tracking finger for opening/closing. - boolean mTracking; + private NotificationGutsManager mGutsManager; + protected NotificationLogger mNotificationLogger; + protected NotificationEntryManager mEntryManager; + protected NotificationViewHierarchyManager mViewHierarchyManager; - int[] mAbsPos = new int[2]; - ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>(); + /** + * Helper that is responsible for showing the right toast when a disallowed activity operation + * occurred. In pinned mode, we show instructions on how to break out of this mode, whilst in + * fully locked mode we only show that unlocking is blocked. + */ + private ScreenPinningNotify mScreenPinningNotify; // for disabling the status bar - int mDisabled1 = 0; - int mDisabled2 = 0; + private int mDisabled1 = 0; + private int mDisabled2 = 0; // tracking calls to View.setSystemUiVisibility() - int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE; + private int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE; private final Rect mLastFullscreenStackBounds = new Rect(); private final Rect mLastDockedStackBounds = new Rect(); private final Rect mTmpRect = new Rect(); @@ -495,7 +427,7 @@ public class StatusBar extends SystemUI implements DemoMode, // last value sent to window manager private int mLastDispatchedSystemUiVisibility = ~View.SYSTEM_UI_FLAG_VISIBLE; - DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); // XXX: gesture research private final GestureRecorder mGestureRec = DEBUG_GESTURES @@ -507,14 +439,17 @@ public class StatusBar extends SystemUI implements DemoMode, private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); // ensure quick settings is disabled until the current user makes it through the setup wizard - private boolean mUserSetup = false; - private DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() { + @VisibleForTesting + protected boolean mUserSetup = false; + private final DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() { @Override public void onUserSetupChanged() { final boolean userSetup = mDeviceProvisionedController.isUserSetup( mDeviceProvisionedController.getCurrentUser()); - if (MULTIUSER_DEBUG) Log.d(TAG, String.format("User setup changed: " + - "userSetup=%s mUserSetup=%s", userSetup, mUserSetup)); + if (MULTIUSER_DEBUG) { + Log.d(TAG, String.format("User setup changed: userSetup=%s mUserSetup=%s", + userSetup, mUserSetup)); + } if (userSetup != mUserSetup) { mUserSetup = userSetup; @@ -528,26 +463,7 @@ public class StatusBar extends SystemUI implements DemoMode, } }; - protected H mHandler = createHandler(); - final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - boolean wasUsing = mUseHeadsUp; - mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts - && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt( - mContext.getContentResolver(), Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, - Settings.Global.HEADS_UP_OFF); - mHeadsUpTicker = mUseHeadsUp && 0 != Settings.Global.getInt( - mContext.getContentResolver(), SETTING_HEADS_UP_TICKER, 0); - Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled")); - if (wasUsing != mUseHeadsUp) { - if (!mUseHeadsUp) { - Log.d(TAG, "dismissing any existing heads up notification on disable event"); - mHeadsUpManager.releaseAllImmediately(); - } - } - } - }; + protected final H mHandler = createHandler(); private int mInteractingWindows; private boolean mAutohideSuspended; @@ -566,63 +482,37 @@ public class StatusBar extends SystemUI implements DemoMode, } }; - private boolean mWaitingForKeyguardExit; protected boolean mDozing; private boolean mDozingRequested; protected boolean mScrimSrcModeEnabled; - public static final Interpolator ALPHA_IN = Interpolators.ALPHA_IN; - public static final Interpolator ALPHA_OUT = Interpolators.ALPHA_OUT; - protected BackDropView mBackdrop; protected ImageView mBackdropFront, mBackdropBack; - protected PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); - protected PorterDuffXfermode mSrcOverXferMode = + protected final PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); + protected final PorterDuffXfermode mSrcOverXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER); - private MediaSessionManager mMediaSessionManager; - private MediaController mMediaController; - private String mMediaNotificationKey; - private MediaMetadata mMediaMetadata; - private MediaController.Callback mMediaListener - = new MediaController.Callback() { + private NotificationMediaManager mMediaManager; + protected NotificationLockscreenUserManager mLockscreenUserManager; + protected NotificationRemoteInputManager mRemoteInputManager; + + private BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() { @Override - public void onPlaybackStateChanged(PlaybackState state) { - super.onPlaybackStateChanged(state); - if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state); - if (state != null) { - if (!isPlaybackActive(state.getState())) { - clearCurrentMediaNotification(); - updateMediaMetaData(true, true); - } + public void onReceive(Context context, Intent intent) { + WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class); + if (wallpaperManager == null) { + Log.w(TAG, "WallpaperManager not available"); + return; } - } + WallpaperInfo info = wallpaperManager.getWallpaperInfo(); + final boolean supportsAmbientMode = info != null && + info.getSupportsAmbientMode(); - @Override - public void onMetadataChanged(MediaMetadata metadata) { - super.onMetadataChanged(metadata); - if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); - mMediaMetadata = metadata; - updateMediaMetaData(true, true); + mStatusBarWindowManager.setWallpaperSupportsAmbientMode(supportsAmbientMode); + mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode); } }; - private final OnChildLocationsChangedListener mOnChildLocationsChangedListener = - new OnChildLocationsChangedListener() { - @Override - public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout) { - userActivity(); - } - }; - - private int mDisabledUnmodified1; - private int mDisabledUnmodified2; - - /** Keys of notifications currently visible to the user. */ - private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications = - new ArraySet<>(); - private long mLastVisibilityReportUptimeMs; - private Runnable mLaunchTransitionEndRunnable; protected boolean mLaunchTransitionFadingAway; private ExpandableNotificationRow mDraggedDownRow; @@ -644,94 +534,29 @@ public class StatusBar extends SystemUI implements DemoMode, private boolean mWereIconsJustHidden; private boolean mBouncerWasShowingWhenHidden; - public boolean isStartedGoingToSleep() { - return mStartedGoingToSleep; - } - - /** - * If set, the device has started going to sleep but isn't fully non-interactive yet. - */ - protected boolean mStartedGoingToSleep; - - private final OnChildLocationsChangedListener mNotificationLocationsChangedListener = - new OnChildLocationsChangedListener() { - @Override - public void onChildLocationsChanged( - NotificationStackScrollLayout stackScrollLayout) { - if (mHandler.hasCallbacks(mVisibilityReporter)) { - // Visibilities will be reported when the existing - // callback is executed. - return; - } - // Calculate when we're allowed to run the visibility - // reporter. Note that this timestamp might already have - // passed. That's OK, the callback will just be executed - // ASAP. - long nextReportUptimeMs = - mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS; - mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs); - } - }; - - // Tracks notifications currently visible in mNotificationStackScroller and - // emits visibility events via NoMan on changes. - protected final Runnable mVisibilityReporter = new Runnable() { - private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications = - new ArraySet<>(); - private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications = - new ArraySet<>(); - private final ArraySet<NotificationVisibility> mTmpNoLongerVisibleNotifications = - new ArraySet<>(); - + // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over, + // this animation is tied to the scrim for historic reasons. + // TODO: notify when keyguard has faded away instead of the scrim. + private final ScrimController.Callback mUnlockScrimCallback = new ScrimController + .Callback() { @Override - public void run() { - mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis(); - final String mediaKey = getCurrentMediaNotificationKey(); - - // 1. Loop over mNotificationData entries: - // A. Keep list of visible notifications. - // B. Keep list of previously hidden, now visible notifications. - // 2. Compute no-longer visible notifications by removing currently - // visible notifications from the set of previously visible - // notifications. - // 3. Report newly visible and no-longer visible notifications. - // 4. Keep currently visible notifications for next report. - ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); - int N = activeNotifications.size(); - for (int i = 0; i < N; i++) { - Entry entry = activeNotifications.get(i); - String key = entry.notification.getKey(); - boolean isVisible = mStackScroller.isInVisibleLocation(entry.row); - NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible); - boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj); - if (isVisible) { - // Build new set of visible notifications. - mTmpCurrentlyVisibleNotifications.add(visObj); - if (!previouslyVisible) { - mTmpNewlyVisibleNotifications.add(visObj); - } - } else { - // release object - visObj.recycle(); - } + public void onFinished() { + if (mStatusBarKeyguardViewManager == null) { + Log.w(TAG, "Tried to notify keyguard visibility when " + + "mStatusBarKeyguardViewManager was null"); + return; } - mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications); - mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications); - - logNotificationVisibilityChanges( - mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications); - - recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); - mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications); + if (mKeyguardFadingAway) { + mStatusBarKeyguardViewManager.onKeyguardFadedAway(); + } + } - recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications); - mTmpCurrentlyVisibleNotifications.clear(); - mTmpNewlyVisibleNotifications.clear(); - mTmpNoLongerVisibleNotifications.clear(); + @Override + public void onCancelled() { + onFinished(); } }; - private NotificationMessagingUtil mMessagingUtil; private KeyguardUserSwitcher mKeyguardUserSwitcher; private UserSwitcherController mUserSwitcherController; private NetworkController mNetworkController; @@ -743,52 +568,46 @@ public class StatusBar extends SystemUI implements DemoMode, private boolean mKeyguardRequested; private boolean mIsKeyguard; private LogMaker mStatusBarStateLog; - private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); + private final LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); protected NotificationIconAreaController mNotificationIconAreaController; private boolean mReinflateNotificationsOnUserSwitched; - private HashMap<String, Entry> mPendingNotifications = new HashMap<>(); private boolean mClearAllEnabled; @Nullable private View mAmbientIndicationContainer; - private String mKeyToRemoveOnGutsClosed; private SysuiColorExtractor mColorExtractor; - private ForegroundServiceController mForegroundServiceController; private ScreenLifecycle mScreenLifecycle; @VisibleForTesting WakefulnessLifecycle mWakefulnessLifecycle; - private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) { - final int N = array.size(); - for (int i = 0 ; i < N; i++) { - array.valueAt(i).recycle(); - } - array.clear(); - } - private final View.OnClickListener mGoToLockedShadeListener = v -> { if (mState == StatusBarState.KEYGUARD) { wakeUpIfDozing(SystemClock.uptimeMillis(), v); goToLockedShade(null); } }; - private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap - = new HashMap<>(); - private RankingMap mLatestRankingMap; private boolean mNoAnimationOnNextBarModeChange; private FalsingManager mFalsingManager; - private KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onDreamingStateChanged(boolean dreaming) { - if (dreaming) { - maybeEscalateHeadsUp(); - } - } - }; + private final KeyguardUpdateMonitorCallback mUpdateCallback = + new KeyguardUpdateMonitorCallback() { + @Override + public void onDreamingStateChanged(boolean dreaming) { + if (dreaming) { + maybeEscalateHeadsUp(); + } + } + }; private NavigationBarFragment mNavigationBar; private View mNavigationBarView; + protected ActivityLaunchAnimator mActivityLaunchAnimator; @Override public void start() { + mGroupManager = Dependency.get(NotificationGroupManager.class); + mVisualStabilityManager = Dependency.get(VisualStabilityManager.class); + mNotificationLogger = Dependency.get(NotificationLogger.class); + mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class); + mNotificationListener = Dependency.get(NotificationListener.class); + mGroupManager = Dependency.get(NotificationGroupManager.class); mNetworkController = Dependency.get(NetworkController.class); mUserSwitcherController = Dependency.get(UserSwitcherController.class); mScreenLifecycle = Dependency.get(ScreenLifecycle.class); @@ -797,25 +616,25 @@ public class StatusBar extends SystemUI implements DemoMode, mWakefulnessLifecycle.addObserver(mWakefulnessObserver); mBatteryController = Dependency.get(BatteryController.class); mAssistManager = Dependency.get(AssistManager.class); - mSystemServicesProxy = SystemServicesProxy.getInstance(mContext); mOverlayManager = IOverlayManager.Stub.asInterface( ServiceManager.getService(Context.OVERLAY_SERVICE)); + mLockscreenUserManager = Dependency.get(NotificationLockscreenUserManager.class); + mGutsManager = Dependency.get(NotificationGutsManager.class); + mMediaManager = Dependency.get(NotificationMediaManager.class); + mEntryManager = Dependency.get(NotificationEntryManager.class); + mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class); mColorExtractor = Dependency.get(SysuiColorExtractor.class); mColorExtractor.addOnColorsChangedListener(this); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - mForegroundServiceController = Dependency.get(ForegroundServiceController.class); - mDisplay = mWindowManager.getDefaultDisplay(); updateDisplaySize(); Resources res = mContext.getResources(); mScrimSrcModeEnabled = res.getBoolean(R.bool.config_status_bar_scrim_behind_use_src); mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll); - mAlwaysExpandNonGroupedNotification = - res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications); DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER)); putComponent(StatusBar.class, this); @@ -825,50 +644,23 @@ public class StatusBar extends SystemUI implements DemoMode, mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService( Context.DEVICE_POLICY_SERVICE); - mNotificationData = new NotificationData(this); - mMessagingUtil = new NotificationMessagingUtil(mContext); - mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); - mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, - mSettingsObserver); - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false, - mLockscreenSettingsObserver, - UserHandle.USER_ALL); - if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) { - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT), - false, - mSettingsObserver, - UserHandle.USER_ALL); - } - - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), - true, - mLockscreenSettingsObserver, - UserHandle.USER_ALL); mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mRecents = getComponent(Recents.class); - final Configuration currentConfig = res.getConfiguration(); - mLocale = currentConfig.locale; - mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale); - - mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mLockPatternUtils = new LockPatternUtils(mContext); + mMediaManager.setUpWithPresenter(this, mEntryManager); + // Connect in to the status bar manager service mCommandQueue = getComponent(CommandQueue.class); mCommandQueue.addCallbacks(this); @@ -888,7 +680,12 @@ public class StatusBar extends SystemUI implements DemoMode, createAndAddWindows(); - mSettingsObserver.onChange(false); // set up + // Make sure we always have the most current wallpaper info. + IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); + mContext.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter); + mWallpaperChangedReceiver.onReceive(mContext, null); + + mLockscreenUserManager.setUpWithPresenter(this, mEntryManager); mCommandQueue.disable(switches[0], switches[6], false /* animate */); setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff, fullscreenStackBounds, dockedStackBounds); @@ -903,14 +700,7 @@ public class StatusBar extends SystemUI implements DemoMode, } // Set up the initial notification state. - try { - mNotificationListener.registerAsSystemService(mContext, - new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()), - UserHandle.USER_ALL); - } catch (RemoteException e) { - Log.e(TAG, "Unable to register notification listener", e); - } - + mNotificationListener.setUpWithPresenter(this, mEntryManager); if (DEBUG) { Log.d(TAG, String.format( @@ -923,28 +713,13 @@ public class StatusBar extends SystemUI implements DemoMode, )); } - mCurrentUserId = ActivityManager.getCurrentUser(); - setHeadsUpUser(mCurrentUserId); - - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_USER_SWITCHED); - filter.addAction(Intent.ACTION_USER_ADDED); - filter.addAction(Intent.ACTION_USER_PRESENT); - mContext.registerReceiver(mBaseBroadcastReceiver, filter); + setHeadsUpUser(mLockscreenUserManager.getCurrentUserId()); IntentFilter internalFilter = new IntentFilter(); - internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION); internalFilter.addAction(BANNER_ACTION_CANCEL); internalFilter.addAction(BANNER_ACTION_SETUP); - mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null); - - IntentFilter allUsersFilter = new IntentFilter(); - allUsersFilter.addAction( - DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); - allUsersFilter.addAction(Intent.ACTION_DEVICE_LOCKED_CHANGED); - mContext.registerReceiverAsUser(mAllUsersReceiver, UserHandle.ALL, allUsersFilter, - null, null); - updateCurrentProfilesCache(); + mContext.registerReceiver(mBannerActionBroadcastReceiver, internalFilter, PERMISSION_SELF, + null); IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService( Context.VR_SERVICE)); @@ -954,29 +729,11 @@ public class StatusBar extends SystemUI implements DemoMode, Slog.e(TAG, "Failed to register VR mode state listener: " + e); } - mNonBlockablePkgs = new HashSet<>(); - Collections.addAll(mNonBlockablePkgs, res.getStringArray( - com.android.internal.R.array.config_nonBlockableNotificationPackages)); // end old BaseStatusBar.start(). - mMediaSessionManager - = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); - // TODO: use MediaSessionManager.SessionListener to hook us up to future updates - // in session state - // Lastly, call to the icon policy to install/update all the icons. mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController); - mSettingsObserver.onChange(false); // set up - mHeadsUpObserver.onChange(true); // set up - if (ENABLE_HEADS_UP) { - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), true, - mHeadsUpObserver); - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true, - mHeadsUpObserver); - } mUnlockMethodCache = UnlockMethodCache.getInstance(mContext); mUnlockMethodCache.addListener(this); startKeyguard(); @@ -994,9 +751,6 @@ public class StatusBar extends SystemUI implements DemoMode, Dependency.get(ConfigurationController.class).addCallback(this); } - protected void createIconController() { - } - // ================================================================================ // Constructing the view // ================================================================================ @@ -1012,16 +766,27 @@ public class StatusBar extends SystemUI implements DemoMode, // TODO: Deal with the ugliness that comes from having some of the statusbar broken out // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot. - mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById( - R.id.notification_panel); - mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById( - R.id.notification_stack_scroller); + mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel); + mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller); + mActivityLaunchAnimator = new ActivityLaunchAnimator(mStatusBarWindow, + this, + mNotificationPanel, + mStackScroller); + mGutsManager.setUpWithPresenter(this, mEntryManager, mStackScroller, mCheckSaveListener, + key -> { + try { + mBarService.onNotificationSettingsViewed(key); + } catch (RemoteException e) { + // if we're here we're dead + } + }); + mNotificationLogger.setUpWithEntryManager(mEntryManager, mStackScroller); mNotificationPanel.setStatusBar(this); mNotificationPanel.setGroupManager(mGroupManager); mAboveShelfObserver = new AboveShelfObserver(mStackScroller); mAboveShelfObserver.setListener(mStatusBarWindow.findViewById( R.id.notification_container_parent)); - mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header); + mKeyguardStatusBar = mStatusBarWindow.findViewById(R.id.keyguard_header); mNotificationIconAreaController = SystemUIFactory.getInstance() .createNotificationIconAreaController(context, this); @@ -1047,20 +812,21 @@ public class StatusBar extends SystemUI implements DemoMode, .commit(); mIconController = Dependency.get(StatusBarIconController.class); - mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow, mGroupManager); - mHeadsUpManager.setBar(this); + mHeadsUpManager = new HeadsUpManagerPhone(context, mStatusBarWindow, mGroupManager, this, + mVisualStabilityManager); mHeadsUpManager.addListener(this); mHeadsUpManager.addListener(mNotificationPanel); mHeadsUpManager.addListener(mGroupManager); mHeadsUpManager.addListener(mVisualStabilityManager); mNotificationPanel.setHeadsUpManager(mHeadsUpManager); - mNotificationData.setHeadsUpManager(mHeadsUpManager); mGroupManager.setHeadsUpManager(mHeadsUpManager); - mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager); + putComponent(HeadsUpManager.class, mHeadsUpManager); + + mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager); + mViewHierarchyManager.setUpWithPresenter(this, mEntryManager, mStackScroller); if (MULTIUSER_DEBUG) { - mNotificationPanelDebugText = (TextView) mNotificationPanel.findViewById( - R.id.header_debug_info); + mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info); mNotificationPanelDebugText.setVisibility(View.VISIBLE); } @@ -1073,11 +839,8 @@ public class StatusBar extends SystemUI implements DemoMode, } catch (RemoteException ex) { // no window manager? good luck with that } - - // figure out which pixel-format to use for the status bar. - mPixelFormat = PixelFormat.OPAQUE; - - mStackScroller.setLongPressListener(getNotificationLongClicker()); + mScreenPinningNotify = new ScreenPinningNotify(mContext); + mStackScroller.setLongPressListener(mEntryManager.getNotificationLongClicker()); mStackScroller.setStatusBar(this); mStackScroller.setGroupManager(mGroupManager); mStackScroller.setHeadsUpManager(mHeadsUpManager); @@ -1086,11 +849,10 @@ public class StatusBar extends SystemUI implements DemoMode, inflateEmptyShadeView(); inflateDismissView(); - mExpandedContents = mStackScroller; - mBackdrop = (BackDropView) mStatusBarWindow.findViewById(R.id.backdrop); - mBackdropFront = (ImageView) mBackdrop.findViewById(R.id.backdrop_front); - mBackdropBack = (ImageView) mBackdrop.findViewById(R.id.backdrop_back); + mBackdrop = mStatusBarWindow.findViewById(R.id.backdrop); + mBackdropFront = mBackdrop.findViewById(R.id.backdrop_front); + mBackdropBack = mBackdrop.findViewById(R.id.backdrop_back); if (ENABLE_LOCKSCREEN_WALLPAPER) { mLockscreenWallpaper = new LockscreenWallpaper(mContext, this, mHandler); @@ -1098,8 +860,8 @@ public class StatusBar extends SystemUI implements DemoMode, mKeyguardIndicationController = SystemUIFactory.getInstance().createKeyguardIndicationController(mContext, - (ViewGroup) mStatusBarWindow.findViewById(R.id.keyguard_indication_area), - mNotificationPanel.getLockIcon()); + mStatusBarWindow.findViewById(R.id.keyguard_indication_area), + mNotificationPanel.getLockIcon()); mNotificationPanel.setKeyguardIndicationController(mKeyguardIndicationController); @@ -1130,24 +892,21 @@ public class StatusBar extends SystemUI implements DemoMode, mNavigationBar.setLightBarController(mLightBarController); } - ScrimView scrimBehind = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_behind); - ScrimView scrimInFront = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_in_front); + ScrimView scrimBehind = mStatusBarWindow.findViewById(R.id.scrim_behind); + ScrimView scrimInFront = mStatusBarWindow.findViewById(R.id.scrim_in_front); View headsUpScrim = mStatusBarWindow.findViewById(R.id.heads_up_scrim); mScrimController = SystemUIFactory.getInstance().createScrimController(mLightBarController, scrimBehind, scrimInFront, headsUpScrim, mLockscreenWallpaper, scrimsVisible -> { if (mStatusBarWindowManager != null) { - mStatusBarWindowManager.setScrimsVisible(scrimsVisible); + mStatusBarWindowManager.setScrimsVisibility(scrimsVisible); } - }); + }, new DozeParameters(mContext), mContext.getSystemService(AlarmManager.class)); if (mScrimSrcModeEnabled) { - Runnable runnable = new Runnable() { - @Override - public void run() { - boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE; - mScrimController.setDrawBehindAsSrc(asSrc); - mStackScroller.setDrawBackgroundAsSrc(asSrc); - } + Runnable runnable = () -> { + boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE; + mScrimController.setDrawBehindAsSrc(asSrc); + mStackScroller.setDrawBackgroundAsSrc(asSrc); }; mBackdrop.setOnVisibilityChangedRunnable(runnable); runnable.run(); @@ -1169,16 +928,19 @@ public class StatusBar extends SystemUI implements DemoMode, if (container != null) { FragmentHostManager fragmentHostManager = FragmentHostManager.get(container); ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame, - Dependency.get(ExtensionController.class).newExtension(QS.class) + Dependency.get(ExtensionController.class) + .newExtension(QS.class) .withPlugin(QS.class) - .withFeature( - PackageManager.FEATURE_AUTOMOTIVE, () -> new CarQSFragment()) - .withDefault(() -> new QSFragment()) + .withFeature(PackageManager.FEATURE_AUTOMOTIVE, CarQSFragment::new) + .withDefault(QSFragment::new) .build()); final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this, mIconController); mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow, - mScrimController); + (visible) -> { + mBrightnessMirrorVisible = visible; + updateScrimController(); + }); fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> { QS qs = (QS) f; if (qs instanceof QSFragment) { @@ -1275,8 +1037,8 @@ public class StatusBar extends SystemUI implements DemoMode, */ protected View.OnTouchListener getStatusBarWindowTouchListener() { return (v, event) -> { - checkUserAutohide(v, event); - checkRemoteInputOutside(event); + checkUserAutohide(event); + mRemoteInputManager.checkRemoteInputOutside(event); if (event.getAction() == MotionEvent.ACTION_DOWN) { if (mExpandedVisible) { animateCollapsePanels(); @@ -1297,9 +1059,11 @@ public class StatusBar extends SystemUI implements DemoMode, } public void onDensityOrFontScaleChanged() { + MessagingMessage.dropCache(); + MessagingGroup.dropCache(); // start old BaseStatusBar.onDensityOrFontScaleChanged(). if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) { - updateNotificationsOnDensityOrFontScaleChanged(); + mEntryManager.updateNotificationsOnDensityOrFontScaleChanged(); } else { mReinflateNotificationsOnUserSwitched = true; } @@ -1323,13 +1087,13 @@ public class StatusBar extends SystemUI implements DemoMode, reevaluateStyles(); } - private void reinflateViews() { + private void onThemeChanged() { reevaluateStyles(); // Clock and bottom icons - mNotificationPanel.onOverlayChanged(); + mNotificationPanel.onThemeChanged(); // The status bar on the keyguard is a special layout. - if (mKeyguardStatusBar != null) mKeyguardStatusBar.onOverlayChanged(); + if (mKeyguardStatusBar != null) mKeyguardStatusBar.onThemeChanged(); // Recreate Indication controller because internal references changed mKeyguardIndicationController = SystemUIFactory.getInstance().createKeyguardIndicationController(mContext, @@ -1340,11 +1104,8 @@ public class StatusBar extends SystemUI implements DemoMode, .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); mKeyguardIndicationController.setVisible(mState == StatusBarState.KEYGUARD); mKeyguardIndicationController.setDozing(mDozing); - if (mBrightnessMirrorController != null) { - mBrightnessMirrorController.onOverlayChanged(); - } if (mStatusBarKeyguardViewManager != null) { - mStatusBarKeyguardViewManager.onOverlayChanged(); + mStatusBarKeyguardViewManager.onThemeChanged(); } if (mAmbientIndicationContainer instanceof AutoReinflateContainer) { ((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout(); @@ -1359,17 +1120,10 @@ public class StatusBar extends SystemUI implements DemoMode, updateEmptyShadeView(); } - private void updateNotificationsOnDensityOrFontScaleChanged() { - ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); - for (int i = 0; i < activeNotifications.size(); i++) { - Entry entry = activeNotifications.get(i); - boolean exposedGuts = mNotificationGutsExposed != null - && entry.row.getGuts() == mNotificationGutsExposed; - entry.row.onDensityOrFontScaleChanged(); - if (exposedGuts) { - mNotificationGutsExposed = entry.row.getGuts(); - bindGuts(entry.row, mGutsMenuItem); - } + @Override + public void onOverlayChanged() { + if (mBrightnessMirrorController != null) { + mBrightnessMirrorController.onOverlayChanged(); } } @@ -1379,8 +1133,7 @@ public class StatusBar extends SystemUI implements DemoMode, public static SignalClusterView reinflateSignalCluster(View view) { Context context = view.getContext(); - SignalClusterView signalCluster = - (SignalClusterView) view.findViewById(R.id.signal_cluster); + SignalClusterView signalCluster = view.findViewById(R.id.signal_cluster); if (signalCluster != null) { ViewParent parent = signalCluster.getParent(); if (parent instanceof ViewGroup) { @@ -1420,20 +1173,17 @@ public class StatusBar extends SystemUI implements DemoMode, mDismissView = (DismissView) LayoutInflater.from(mContext).inflate( R.layout.status_bar_notification_dismiss_all, mStackScroller, false); - mDismissView.setOnButtonClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES); - clearAllNotifications(); - } + mDismissView.setOnButtonClickListener(v -> { + mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES); + clearAllNotifications(); }); mStackScroller.setDismissView(mDismissView); } protected void createUserSwitcher() { mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext, - (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher), - mKeyguardStatusBar, mNotificationPanel); + mStatusBarWindow.findViewById(R.id.keyguard_user_switcher), mKeyguardStatusBar, + mNotificationPanel); } protected void inflateStatusBarWindow(Context context) { @@ -1446,7 +1196,7 @@ public class StatusBar extends SystemUI implements DemoMode, // animate-swipe all dismissable notifications, then animate the shade closed int numChildren = mStackScroller.getChildCount(); - final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren); + final ArrayList<View> viewsToHide = new ArrayList<>(numChildren); final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren); for (int i = 0; i < numChildren; i++) { final View child = mStackScroller.getChildAt(i); @@ -1486,20 +1236,18 @@ public class StatusBar extends SystemUI implements DemoMode, return; } - addPostCollapseAction(new Runnable() { - @Override - public void run() { - mStackScroller.setDismissAllInProgress(false); - for (ExpandableNotificationRow rowToRemove : viewsToRemove) { - if (mStackScroller.canChildBeDismissed(rowToRemove)) { - removeNotification(rowToRemove.getEntry().key, null); - } else { - rowToRemove.resetTranslation(); - } + addPostCollapseAction(() -> { + mStackScroller.setDismissAllInProgress(false); + for (ExpandableNotificationRow rowToRemove : viewsToRemove) { + if (mStackScroller.canChildBeDismissed(rowToRemove)) { + mEntryManager.removeNotification(rowToRemove.getEntry().key, null); + } else { + rowToRemove.resetTranslation(); } - try { - mBarService.onClearAllNotifications(mCurrentUserId); - } catch (Exception ex) { } + } + try { + mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId()); + } catch (Exception ex) { } }); @@ -1508,11 +1256,8 @@ public class StatusBar extends SystemUI implements DemoMode, } private void performDismissAllAnimations(ArrayList<View> hideAnimatedList) { - Runnable animationFinishAction = new Runnable() { - @Override - public void run() { - animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); - } + Runnable animationFinishAction = () -> { + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); }; if (hideAnimatedList.isEmpty()) { @@ -1541,15 +1286,6 @@ public class StatusBar extends SystemUI implements DemoMode, } } - protected void setZenMode(int mode) { - // start old BaseStatusBar.setZenMode(). - if (isDeviceProvisioned()) { - mZenMode = mode; - updateNotifications(); - } - // end old BaseStatusBar.setZenMode(). - } - protected void startKeyguard() { Trace.beginSection("StatusBar#startKeyguard"); KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class); @@ -1557,31 +1293,11 @@ public class StatusBar extends SystemUI implements DemoMode, mDozeScrimController, keyguardViewMediator, mScrimController, this, UnlockMethodCache.getInstance(mContext)); mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this, - getBouncerContainer(), mScrimController, - mFingerprintUnlockController); + getBouncerContainer(), mFingerprintUnlockController); mKeyguardIndicationController .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); - mRemoteInputController.addCallback(mStatusBarKeyguardViewManager); - - mRemoteInputController.addCallback(new RemoteInputController.Callback() { - @Override - public void onRemoteInputSent(Entry entry) { - if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) { - removeNotification(entry.key, null); - } else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) { - // We're currently holding onto this notification, but from the apps point of - // view it is already canceled, so we'll need to cancel it on the apps behalf - // after sending - unless the app posts an update in the mean time, so wait a - // bit. - mHandler.postDelayed(() -> { - if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) { - removeNotification(entry.key, null); - } - }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY); - } - } - }); + mRemoteInputManager.getController().addCallback(mStatusBarKeyguardViewManager); mKeyguardViewMediatorCallback = keyguardViewMediator.getViewMediatorCallback(); mLightBarController.setFingerprintUnlockController(mFingerprintUnlockController); @@ -1615,8 +1331,8 @@ public class StatusBar extends SystemUI implements DemoMode, } int dockSide = WindowManagerProxy.getInstance().getDockSide(); if (dockSide == WindowManager.DOCKED_INVALID) { - return mRecents.dockTopTask(NavigationBarGestureHelper.DRAG_MODE_NONE, - ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction); + return mRecents.splitPrimaryTask(NavigationBarGestureHelper.DRAG_MODE_NONE, + ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction); } else { Divider divider = getComponent(Divider.class); if (divider != null && divider.isMinimized() && !divider.isHomeStackResizable()) { @@ -1632,213 +1348,54 @@ public class StatusBar extends SystemUI implements DemoMode, return true; } - void awakenDreams() { - SystemServicesProxy.getInstance(mContext).awakenDreamsAsync(); - } - - public UserHandle getCurrentUserHandle() { - return new UserHandle(mCurrentUserId); + @Override + public void onPerformRemoveNotification(StatusBarNotification n) { + if (mStackScroller.hasPulsingNotifications() && + !mHeadsUpManager.hasHeadsUpNotifications()) { + // We were showing a pulse for a notification, but no notifications are pulsing anymore. + // Finish the pulse. + mDozeScrimController.pulseOutNow(); + } } - public void addNotification(StatusBarNotification notification, RankingMap ranking) - throws InflationException { - String key = notification.getKey(); - if (DEBUG) Log.d(TAG, "addNotification key=" + key); - - mNotificationData.updateRanking(ranking); - Entry shadeEntry = createNotificationViews(notification); - boolean isHeadsUped = shouldPeek(shadeEntry); - if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) { - if (shouldSuppressFullScreenIntent(key)) { - if (DEBUG) { - Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key); - } - } else if (mNotificationData.getImportance(key) - < NotificationManager.IMPORTANCE_HIGH) { - if (DEBUG) { - Log.d(TAG, "No Fullscreen intent: not important enough: " - + key); - } - } else { - // Stop screensaver if the notification has a full-screen intent. - // (like an incoming phone call) - awakenDreams(); + @Override + public void updateNotificationViews() { + // The function updateRowStates depends on both of these being non-null, so check them here. + // We may be called before they are set from DeviceProvisionedController's callback. + if (mStackScroller == null || mScrimController == null) return; - // not immersive & a full-screen alert should be shown - if (DEBUG) - Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); - try { - EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, - key); - notification.getNotification().fullScreenIntent.send(); - shadeEntry.notifyFullScreenIntentLaunched(); - mMetricsLogger.count("note_fullscreen", 1); - } catch (PendingIntent.CanceledException e) { - } - } + // Do not modify the notifications during collapse. + if (isCollapsing()) { + addPostCollapseAction(this::updateNotificationViews); + return; } - abortExistingInflation(key); - mForegroundServiceController.addNotification(notification, - mNotificationData.getImportance(key)); + mViewHierarchyManager.updateNotificationViews(); - mPendingNotifications.put(key, shadeEntry); - } + updateSpeedBumpIndex(); + updateClearAll(); + updateEmptyShadeView(); - private void abortExistingInflation(String key) { - if (mPendingNotifications.containsKey(key)) { - Entry entry = mPendingNotifications.get(key); - entry.abortTask(); - mPendingNotifications.remove(key); - } - Entry addedEntry = mNotificationData.get(key); - if (addedEntry != null) { - addedEntry.abortTask(); - } + updateQsExpansionEnabled(); + + // Let's also update the icons + mNotificationIconAreaController.updateNotificationIcons( + mEntryManager.getNotificationData()); } - private void addEntry(Entry shadeEntry) { - boolean isHeadsUped = shouldPeek(shadeEntry); - if (isHeadsUped) { - mHeadsUpManager.showNotification(shadeEntry); - // Mark as seen immediately - setNotificationShown(shadeEntry.notification); - } - addNotificationViews(shadeEntry); + @Override + public void onNotificationAdded(Entry shadeEntry) { // Recalculate the position of the sliding windows and the titles. setAreThereNotifications(); } @Override - public void handleInflationException(StatusBarNotification notification, Exception e) { - handleNotificationError(notification, e.getMessage()); + public void onNotificationUpdated(StatusBarNotification notification) { + setAreThereNotifications(); } @Override - public void onAsyncInflationFinished(Entry entry) { - mPendingNotifications.remove(entry.key); - // If there was an async task started after the removal, we don't want to add it back to - // the list, otherwise we might get leaks. - boolean isNew = mNotificationData.get(entry.key) == null; - if (isNew && !entry.row.isRemoved()) { - addEntry(entry); - } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) { - mVisualStabilityManager.onLowPriorityUpdated(entry); - updateNotificationShade(); - } - entry.row.setLowPriorityStateUpdated(false); - } - - private boolean shouldSuppressFullScreenIntent(String key) { - if (isDeviceInVrMode()) { - return true; - } - - if (mPowerManager.isInteractive()) { - return mNotificationData.shouldSuppressScreenOn(key); - } else { - return mNotificationData.shouldSuppressScreenOff(key); - } - } - - protected void updateNotificationRanking(RankingMap ranking) { - mNotificationData.updateRanking(ranking); - updateNotifications(); - } - - public void removeNotification(String key, RankingMap ranking) { - boolean deferRemoval = false; - abortExistingInflation(key); - if (mHeadsUpManager.isHeadsUp(key)) { - // A cancel() in repsonse to a remote input shouldn't be delayed, as it makes the - // sending look longer than it takes. - // Also we should not defer the removal if reordering isn't allowed since otherwise - // some notifications can't disappear before the panel is closed. - boolean ignoreEarliestRemovalTime = mRemoteInputController.isSpinning(key) - && !FORCE_REMOTE_INPUT_HISTORY - || !mVisualStabilityManager.isReorderingAllowed(); - deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime); - } - if (key.equals(mMediaNotificationKey)) { - clearCurrentMediaNotification(); - updateMediaMetaData(true, true); - } - if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)) { - Entry entry = mNotificationData.get(key); - StatusBarNotification sbn = entry.notification; - - Notification.Builder b = Notification.Builder - .recoverBuilder(mContext, sbn.getNotification().clone()); - CharSequence[] oldHistory = sbn.getNotification().extras - .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); - CharSequence[] newHistory; - if (oldHistory == null) { - newHistory = new CharSequence[1]; - } else { - newHistory = new CharSequence[oldHistory.length + 1]; - for (int i = 0; i < oldHistory.length; i++) { - newHistory[i + 1] = oldHistory[i]; - } - } - newHistory[0] = String.valueOf(entry.remoteInputText); - b.setRemoteInputHistory(newHistory); - - Notification newNotification = b.build(); - - // Undo any compatibility view inflation - newNotification.contentView = sbn.getNotification().contentView; - newNotification.bigContentView = sbn.getNotification().bigContentView; - newNotification.headsUpContentView = sbn.getNotification().headsUpContentView; - - StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(), - sbn.getOpPkg(), - sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), - newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); - boolean updated = false; - try { - updateNotification(newSbn, null); - updated = true; - } catch (InflationException e) { - deferRemoval = false; - } - if (updated) { - mKeysKeptForRemoteInput.add(entry.key); - return; - } - } - if (deferRemoval) { - mLatestRankingMap = ranking; - mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key)); - return; - } - Entry entry = mNotificationData.get(key); - - if (entry != null && mRemoteInputController.isRemoteInputActive(entry) - && (entry.row != null && !entry.row.isDismissed())) { - mLatestRankingMap = ranking; - mRemoteInputEntriesToRemoveOnCollapse.add(entry); - return; - } - if (entry != null && mNotificationGutsExposed != null - && mNotificationGutsExposed == entry.row.getGuts() && entry.row.getGuts() != null - && !entry.row.getGuts().isLeavebehind()) { - Log.w(TAG, "Keeping notification because it's showing guts. " + key); - mLatestRankingMap = ranking; - mKeyToRemoveOnGutsClosed = key; - return; - } - - if (entry != null) { - mForegroundServiceController.removeNotification(entry.notification); - } - - if (entry != null && entry.row != null) { - entry.row.setRemoved(); - mStackScroller.cleanUpViewState(entry.row); - } - // Let's remove the children if this was a summary - handleGroupSummaryRemoved(key, ranking); - StatusBarNotification old = removeNotificationViews(key, ranking); + public void onNotificationRemoved(String key, StatusBarNotification old) { if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); if (old != null) { @@ -1855,213 +1412,6 @@ public class StatusBar extends SystemUI implements DemoMode, } /** - * Ensures that the group children are cancelled immediately when the group summary is cancelled - * instead of waiting for the notification manager to send all cancels. Otherwise this could - * lead to flickers. - * - * This also ensures that the animation looks nice and only consists of a single disappear - * animation instead of multiple. - * - * @param key the key of the notification was removed - * @param ranking the current ranking - */ - private void handleGroupSummaryRemoved(String key, - RankingMap ranking) { - Entry entry = mNotificationData.get(key); - if (entry != null && entry.row != null - && entry.row.isSummaryWithChildren()) { - if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) { - // We don't want to remove children for autobundled notifications as they are not - // always cancelled. We only remove them if they were dismissed by the user. - return; - } - List<ExpandableNotificationRow> notificationChildren = - entry.row.getNotificationChildren(); - ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); - for (int i = 0; i < notificationChildren.size(); i++) { - ExpandableNotificationRow row = notificationChildren.get(i); - if ((row.getStatusBarNotification().getNotification().flags - & Notification.FLAG_FOREGROUND_SERVICE) != 0) { - // the child is a forground service notification which we can't remove! - continue; - } - toRemove.add(row); - row.setKeepInParent(true); - // we need to set this state earlier as otherwise we might generate some weird - // animations - row.setRemoved(); - } - } - } - - protected void performRemoveNotification(StatusBarNotification n) { - Entry entry = mNotificationData.get(n.getKey()); - if (mRemoteInputController.isRemoteInputActive(entry)) { - mRemoteInputController.removeRemoteInput(entry, null); - } - // start old BaseStatusBar.performRemoveNotification. - final String pkg = n.getPackageName(); - final String tag = n.getTag(); - final int id = n.getId(); - final int userId = n.getUserId(); - try { - mBarService.onNotificationClear(pkg, tag, id, userId); - if (FORCE_REMOTE_INPUT_HISTORY - && mKeysKeptForRemoteInput.contains(n.getKey())) { - mKeysKeptForRemoteInput.remove(n.getKey()); - } - removeNotification(n.getKey(), null); - - } catch (RemoteException ex) { - // system process is dead if we're here. - } - if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) { - // We were showing a pulse for a notification, but no notifications are pulsing anymore. - // Finish the pulse. - mDozeScrimController.pulseOutNow(); - } - // end old BaseStatusBar.performRemoveNotification. - } - - private void updateNotificationShade() { - if (mStackScroller == null) return; - - // Do not modify the notifications during collapse. - if (isCollapsing()) { - addPostCollapseAction(new Runnable() { - @Override - public void run() { - updateNotificationShade(); - } - }); - return; - } - - ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); - ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size()); - final int N = activeNotifications.size(); - for (int i=0; i<N; i++) { - Entry ent = activeNotifications.get(i); - if (ent.row.isDismissed() || ent.row.isRemoved()) { - // we don't want to update removed notifications because they could - // temporarily become children if they were isolated before. - continue; - } - int userId = ent.notification.getUserId(); - - // Display public version of the notification if we need to redact. - boolean devicePublic = isLockscreenPublicMode(mCurrentUserId); - boolean userPublic = devicePublic || isLockscreenPublicMode(userId); - boolean needsRedaction = needsRedaction(ent); - boolean sensitive = userPublic && needsRedaction; - boolean deviceSensitive = devicePublic - && !userAllowsPrivateNotificationsInPublic(mCurrentUserId); - ent.row.setSensitive(sensitive, deviceSensitive); - ent.row.setNeedsRedaction(needsRedaction); - if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) { - ExpandableNotificationRow summary = mGroupManager.getGroupSummary( - ent.row.getStatusBarNotification()); - List<ExpandableNotificationRow> orderedChildren = - mTmpChildOrderMap.get(summary); - if (orderedChildren == null) { - orderedChildren = new ArrayList<>(); - mTmpChildOrderMap.put(summary, orderedChildren); - } - orderedChildren.add(ent.row); - } else { - toShow.add(ent.row); - } - - } - - ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); - for (int i=0; i< mStackScroller.getChildCount(); i++) { - View child = mStackScroller.getChildAt(i); - if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) { - toRemove.add((ExpandableNotificationRow) child); - } - } - - for (ExpandableNotificationRow remove : toRemove) { - if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) { - // we are only transfering this notification to its parent, don't generate an animation - mStackScroller.setChildTransferInProgress(true); - } - if (remove.isSummaryWithChildren()) { - remove.removeAllChildren(); - } - mStackScroller.removeView(remove); - mStackScroller.setChildTransferInProgress(false); - } - - removeNotificationChildren(); - - for (int i=0; i<toShow.size(); i++) { - View v = toShow.get(i); - if (v.getParent() == null) { - mVisualStabilityManager.notifyViewAddition(v); - mStackScroller.addView(v); - } - } - - addNotificationChildrenAndSort(); - - // So after all this work notifications still aren't sorted correctly. - // Let's do that now by advancing through toShow and mStackScroller in - // lock-step, making sure mStackScroller matches what we see in toShow. - int j = 0; - for (int i = 0; i < mStackScroller.getChildCount(); i++) { - View child = mStackScroller.getChildAt(i); - if (!(child instanceof ExpandableNotificationRow)) { - // We don't care about non-notification views. - continue; - } - - ExpandableNotificationRow targetChild = toShow.get(j); - if (child != targetChild) { - // Oops, wrong notification at this position. Put the right one - // here and advance both lists. - if (mVisualStabilityManager.canReorderNotification(targetChild)) { - mStackScroller.changeViewPosition(targetChild, i); - } else { - mVisualStabilityManager.addReorderingAllowedCallback(this); - } - } - j++; - - } - - mVisualStabilityManager.onReorderingFinished(); - // clear the map again for the next usage - mTmpChildOrderMap.clear(); - - updateRowStates(); - updateSpeedBumpIndex(); - updateClearAll(); - updateEmptyShadeView(); - - updateQsExpansionEnabled(); - - // Let's also update the icons - mNotificationIconAreaController.updateNotificationIcons(mNotificationData); - } - - /** @return true if the entry needs redaction when on the lockscreen. */ - private boolean needsRedaction(Entry ent) { - int userId = ent.notification.getUserId(); - - boolean currentUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(mCurrentUserId); - boolean notiUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(userId); - boolean redactedLockscreen = currentUserWantsRedaction || notiUserWantsRedaction; - - boolean notificationRequestsRedaction = - ent.notification.getNotification().visibility == Notification.VISIBILITY_PRIVATE; - boolean userForcesRedaction = packageHasVisibilityOverride(ent.notification.getKey()); - - return userForcesRedaction || notificationRequestsRedaction && redactedLockscreen; - } - - /** * Disable QS if device not provisioned. * If the user switcher is simple then disable QS during setup because * the user intends to use the lock screen user switcher, QS in not needed. @@ -2070,86 +1420,12 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationPanel.setQsExpansionEnabled(isDeviceProvisioned() && (mUserSetup || mUserSwitcherController == null || !mUserSwitcherController.isSimpleUserSwitcher()) + && ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0) && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0) && !mDozing && !ONLY_CORE_APPS); } - private void addNotificationChildrenAndSort() { - // Let's now add all notification children which are missing - boolean orderChanged = false; - for (int i = 0; i < mStackScroller.getChildCount(); i++) { - View view = mStackScroller.getChildAt(i); - if (!(view instanceof ExpandableNotificationRow)) { - // We don't care about non-notification views. - continue; - } - - ExpandableNotificationRow parent = (ExpandableNotificationRow) view; - List<ExpandableNotificationRow> children = parent.getNotificationChildren(); - List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); - - for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size(); - childIndex++) { - ExpandableNotificationRow childView = orderedChildren.get(childIndex); - if (children == null || !children.contains(childView)) { - if (childView.getParent() != null) { - Log.wtf(TAG, "trying to add a notification child that already has " + - "a parent. class:" + childView.getParent().getClass() + - "\n child: " + childView); - // This shouldn't happen. We can recover by removing it though. - ((ViewGroup) childView.getParent()).removeView(childView); - } - mVisualStabilityManager.notifyViewAddition(childView); - parent.addChildNotification(childView, childIndex); - mStackScroller.notifyGroupChildAdded(childView); - } - } - - // Finally after removing and adding has been beformed we can apply the order. - orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager, this); - } - if (orderChanged) { - mStackScroller.generateChildOrderChangedEvent(); - } - } - - private void removeNotificationChildren() { - // First let's remove all children which don't belong in the parents - ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); - for (int i = 0; i < mStackScroller.getChildCount(); i++) { - View view = mStackScroller.getChildAt(i); - if (!(view instanceof ExpandableNotificationRow)) { - // We don't care about non-notification views. - continue; - } - - ExpandableNotificationRow parent = (ExpandableNotificationRow) view; - List<ExpandableNotificationRow> children = parent.getNotificationChildren(); - List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); - - if (children != null) { - toRemove.clear(); - for (ExpandableNotificationRow childRow : children) { - if ((orderedChildren == null - || !orderedChildren.contains(childRow)) - && !childRow.keepInParent()) { - toRemove.add(childRow); - } - } - for (ExpandableNotificationRow remove : toRemove) { - parent.removeChildNotification(remove); - if (mNotificationData.get(remove.getStatusBarNotification().getKey()) == null) { - // We only want to add an animation if the view is completely removed - // otherwise it's just a transfer - mStackScroller.notifyGroupChildRemoved(remove, - parent.getChildrenContainer()); - } - } - } - } - } - public void addQsTile(ComponentName tile) { mQSPanel.getHost().addTile(tile); } @@ -2162,10 +1438,6 @@ public class StatusBar extends SystemUI implements DemoMode, mQSPanel.clickTile(tile); } - private boolean packageHasVisibilityOverride(String key) { - return mNotificationData.getVisibilityOverride(key) == Notification.VISIBILITY_PRIVATE; - } - private void updateClearAll() { if (!mClearAllEnabled) { return; @@ -2195,7 +1467,7 @@ public class StatusBar extends SystemUI implements DemoMode, private void updateEmptyShadeView() { boolean showEmptyShadeView = mState != StatusBarState.KEYGUARD && - mNotificationData.getActiveNotifications().size() == 0; + mEntryManager.getNotificationData().getActiveNotifications().size() == 0; mNotificationPanel.showEmptyShadeView(showEmptyShadeView); } @@ -2210,7 +1482,8 @@ public class StatusBar extends SystemUI implements DemoMode, } ExpandableNotificationRow row = (ExpandableNotificationRow) view; currentIndex++; - if (!mNotificationData.isAmbient(row.getStatusBarNotification().getKey())) { + if (!mEntryManager.getNotificationData().isAmbient( + row.getStatusBarNotification().getKey())) { speedBumpIndex = currentIndex; } } @@ -2222,14 +1495,9 @@ public class StatusBar extends SystemUI implements DemoMode, return entry.row.getParent() instanceof NotificationStackScrollLayout; } - protected void updateNotifications() { - mNotificationData.filterAndSort(); - - updateNotificationShade(); - } public void requestNotificationUpdate() { - updateNotifications(); + mEntryManager.updateNotifications(); } protected void setAreThereNotifications() { @@ -2238,7 +1506,7 @@ public class StatusBar extends SystemUI implements DemoMode, final boolean clearable = hasActiveNotifications() && hasActiveClearableNotifications(); Log.d(TAG, "setAreThereNotifications: N=" + - mNotificationData.getActiveNotifications().size() + " any=" + + mEntryManager.getNotificationData().getActiveNotifications().size() + " any=" + hasActiveNotifications() + " clearable=" + clearable); } @@ -2264,144 +1532,14 @@ public class StatusBar extends SystemUI implements DemoMode, } } - findAndUpdateMediaNotifications(); - } - - public void findAndUpdateMediaNotifications() { - boolean metaDataChanged = false; - - synchronized (mNotificationData) { - ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); - final int N = activeNotifications.size(); - - // Promote the media notification with a controller in 'playing' state, if any. - Entry mediaNotification = null; - MediaController controller = null; - for (int i = 0; i < N; i++) { - final Entry entry = activeNotifications.get(i); - if (isMediaNotification(entry)) { - final MediaSession.Token token = - entry.notification.getNotification().extras - .getParcelable(Notification.EXTRA_MEDIA_SESSION); - if (token != null) { - MediaController aController = new MediaController(mContext, token); - if (PlaybackState.STATE_PLAYING == - getMediaControllerPlaybackState(aController)) { - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching " - + entry.notification.getKey()); - } - mediaNotification = entry; - controller = aController; - break; - } - } - } - } - if (mediaNotification == null) { - // Still nothing? OK, let's just look for live media sessions and see if they match - // one of our notifications. This will catch apps that aren't (yet!) using media - // notifications. - - if (mMediaSessionManager != null) { - final List<MediaController> sessions - = mMediaSessionManager.getActiveSessionsForUser( - null, - UserHandle.USER_ALL); - - for (MediaController aController : sessions) { - if (PlaybackState.STATE_PLAYING == - getMediaControllerPlaybackState(aController)) { - // now to see if we have one like this - final String pkg = aController.getPackageName(); - - for (int i = 0; i < N; i++) { - final Entry entry = activeNotifications.get(i); - if (entry.notification.getPackageName().equals(pkg)) { - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: found controller matching " - + entry.notification.getKey()); - } - controller = aController; - mediaNotification = entry; - break; - } - } - } - } - } - } - - if (controller != null && !sameSessions(mMediaController, controller)) { - // We have a new media session - clearCurrentMediaNotification(); - mMediaController = controller; - mMediaController.registerCallback(mMediaListener); - mMediaMetadata = mMediaController.getMetadata(); - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: " - + mMediaMetadata); - } - - if (mediaNotification != null) { - mMediaNotificationKey = mediaNotification.notification.getKey(); - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key=" - + mMediaNotificationKey + " controller=" + mMediaController); - } - } - metaDataChanged = true; - } - } - - if (metaDataChanged) { - updateNotifications(); - } - updateMediaMetaData(metaDataChanged, true); - } - - private int getMediaControllerPlaybackState(MediaController controller) { - if (controller != null) { - final PlaybackState playbackState = controller.getPlaybackState(); - if (playbackState != null) { - return playbackState.getState(); - } - } - return PlaybackState.STATE_NONE; - } - - private boolean isPlaybackActive(int state) { - if (state != PlaybackState.STATE_STOPPED - && state != PlaybackState.STATE_ERROR - && state != PlaybackState.STATE_NONE) { - return true; - } - return false; - } - - private void clearCurrentMediaNotification() { - mMediaNotificationKey = null; - mMediaMetadata = null; - if (mMediaController != null) { - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: " - + mMediaController.getPackageName()); - } - mMediaController.unregisterCallback(mMediaListener); - } - mMediaController = null; + mMediaManager.findAndUpdateMediaNotifications(); } - private boolean sameSessions(MediaController a, MediaController b) { - if (a == b) return true; - if (a == null) return false; - return a.controlsSameSession(b); - } /** * Hide the album artwork that is fading out and release its bitmap. */ - protected Runnable mHideBackdropFront = new Runnable() { + protected final Runnable mHideBackdropFront = new Runnable() { @Override public void run() { if (DEBUG_MEDIA) { @@ -2413,9 +1551,11 @@ public class StatusBar extends SystemUI implements DemoMode, } }; + // TODO: Move this to NotificationMediaManager. /** * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper. */ + @Override public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { Trace.beginSection("StatusBar#updateMediaMetaData"); if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) { @@ -2434,19 +1574,22 @@ public class StatusBar extends SystemUI implements DemoMode, return; } + MediaMetadata mediaMetadata = mMediaManager.getMediaMetadata(); + if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: updating album art for notification " + mMediaNotificationKey - + " metadata=" + mMediaMetadata + Log.v(TAG, "DEBUG_MEDIA: updating album art for notification " + + mMediaManager.getMediaNotificationKey() + + " metadata=" + mediaMetadata + " metaDataChanged=" + metaDataChanged + " state=" + mState); } Drawable artworkDrawable = null; - if (mMediaMetadata != null) { + if (mediaMetadata != null) { Bitmap artworkBitmap = null; - artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART); + artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART); if (artworkBitmap == null) { - artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); + artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); // might still be null } if (artworkBitmap != null) { @@ -2471,7 +1614,7 @@ public class StatusBar extends SystemUI implements DemoMode, final boolean hasArtwork = artworkDrawable != null; - if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) + if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) && !mDozing && (mState != StatusBarState.SHADE || allowWhenShade) && mFingerprintUnlockController.getMode() != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING @@ -2532,15 +1675,16 @@ public class StatusBar extends SystemUI implements DemoMode, } } } else { - // need to hide the album art, either because we are unlocked or because - // the metadata isn't there to support it + // need to hide the album art, either because we are unlocked, on AOD + // or because the metadata isn't there to support it if (mBackdrop.getVisibility() != View.GONE) { if (DEBUG_MEDIA) { Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork"); } + boolean cannotAnimateDoze = mDozing && !ScrimState.AOD.getAnimateChange(); if (mFingerprintUnlockController.getMode() == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING - || hideBecauseOccluded) { + || hideBecauseOccluded || cannotAnimateDoze) { // We are unlocking directly - no animation! mBackdrop.setVisibility(View.GONE); @@ -2553,14 +1697,11 @@ public class StatusBar extends SystemUI implements DemoMode, .setInterpolator(Interpolators.ACCELERATE_DECELERATE) .setDuration(300) .setStartDelay(0) - .withEndAction(new Runnable() { - @Override - public void run() { - mBackdrop.setVisibility(View.GONE); - mBackdropFront.animate().cancel(); - mBackdropBack.setImageDrawable(null); - mHandler.post(mHideBackdropFront); - } + .withEndAction(() -> { + mBackdrop.setVisibility(View.GONE); + mBackdropFront.animate().cancel(); + mBackdropBack.setImageDrawable(null); + mHandler.post(mHideBackdropFront); }); if (mKeyguardFadingAway) { mBackdrop.animate() @@ -2581,7 +1722,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (mReportRejectedTouch == null) { return; } - mReportRejectedTouch.setVisibility(mState == StatusBarState.KEYGUARD + mReportRejectedTouch.setVisibility(mState == StatusBarState.KEYGUARD && !mDozing && mFalsingManager.isReportingEnabled() ? View.VISIBLE : View.INVISIBLE); } @@ -2591,8 +1732,6 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void disable(int state1, int state2, boolean animate) { animate &= mStatusBarWindowState != WINDOW_STATE_HIDDEN; - mDisabledUnmodified1 = state1; - mDisabledUnmodified2 = state2; final int old1 = mDisabled1; final int diff1 = state1 ^ old1; mDisabled1 = state1; @@ -2628,8 +1767,13 @@ public class StatusBar extends SystemUI implements DemoMode, flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_CLOCK)) ? '!' : ' '); flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_SEARCH)) ? 'S' : 's'); flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_SEARCH)) ? '!' : ' '); + flagdbg.append("> disable2<"); flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_QUICK_SETTINGS)) ? 'Q' : 'q'); flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS)) ? '!' : ' '); + flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_SYSTEM_ICONS)) ? 'I' : 'i'); + flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_SYSTEM_ICONS)) ? '!' : ' '); + flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE)) ? 'N' : 'n'); + flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE)) ? '!' : ' '); flagdbg.append('>'); Log.d(TAG, flagdbg.toString()); @@ -2648,14 +1792,20 @@ public class StatusBar extends SystemUI implements DemoMode, } if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) { - mDisableNotificationAlerts = - (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0; - mHeadsUpObserver.onChange(true); + mEntryManager.setDisableNotificationAlerts( + (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0); } if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) { updateQsExpansionEnabled(); } + + if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) { + updateQsExpansionEnabled(); + if ((state1 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) { + animateCollapsePanels(); + } + } } /** @@ -2706,13 +1856,43 @@ public class StatusBar extends SystemUI implements DemoMode, return getBarState() == StatusBarState.KEYGUARD; } + @Override public boolean isDozing() { return mDozing; } + @Override + public boolean shouldPeek(Entry entry, StatusBarNotification sbn) { + if (mIsOccluded && !isDozing()) { + boolean devicePublic = mLockscreenUserManager. + isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId()); + boolean userPublic = devicePublic + || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId()); + boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry); + if (userPublic && needsRedaction) { + return false; + } + } + + if (sbn.getNotification().fullScreenIntent != null) { + if (mAccessibilityManager.isTouchExplorationEnabled()) { + if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey()); + return false; + } else if (isDozing()) { + // We never want heads up when we are dozing. + return false; + } else { + // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent + return !mStatusBarKeyguardViewManager.isShowing() + || mStatusBarKeyguardViewManager.isOccluded(); + } + } + return true; + } + @Override // NotificationData.Environment public String getCurrentMediaNotificationKey() { - return mMediaNotificationKey; + return mMediaManager.getMediaNotificationKey(); } public boolean isScrimSrcModeEnabled() { @@ -2743,11 +1923,8 @@ public class StatusBar extends SystemUI implements DemoMode, // make sure that the window stays small for one frame until the touchableRegion is set. mNotificationPanel.requestLayout(); mStatusBarWindowManager.setForceWindowCollapsed(true); - mNotificationPanel.post(new Runnable() { - @Override - public void run() { - mStatusBarWindowManager.setForceWindowCollapsed(false); - } + mNotificationPanel.post(() -> { + mStatusBarWindowManager.setForceWindowCollapsed(false); }); } } else { @@ -2759,15 +1936,12 @@ public class StatusBar extends SystemUI implements DemoMode, // we need to keep the panel open artificially, let's wait until the animation // is finished. mHeadsUpManager.setHeadsUpGoingAway(true); - mStackScroller.runAfterAnimationFinished(new Runnable() { - @Override - public void run() { - if (!mHeadsUpManager.hasPinnedHeadsUp()) { - mStatusBarWindowManager.setHeadsUpShowing(false); - mHeadsUpManager.setHeadsUpGoingAway(false); - } - removeRemoteInputEntriesKeptUntilCollapsed(); + mStackScroller.runAfterAnimationFinished(() -> { + if (!mHeadsUpManager.hasPinnedHeadsUp()) { + mStatusBarWindowManager.setHeadsUpShowing(false); + mHeadsUpManager.setHeadsUpGoingAway(false); } + mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed(); }); } } @@ -2784,34 +1958,10 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) { - if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) { - removeNotification(entry.key, mLatestRankingMap); - mHeadsUpEntriesToRemoveOnSwitch.remove(entry); - if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) { - mLatestRankingMap = null; - } - } else { - updateNotificationRanking(null); - if (isHeadsUp) { - mDozeServiceHost.fireNotificationHeadsUp(); - } - } - - } + mEntryManager.onHeadsUpStateChanged(entry, isHeadsUp); - protected void updateHeadsUp(String key, Entry entry, boolean shouldPeek, - boolean alertAgain) { - final boolean wasHeadsUp = isHeadsUp(key); - if (wasHeadsUp) { - if (!shouldPeek) { - // We don't want this to be interrupting anymore, lets remove it - mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */); - } else { - mHeadsUpManager.updateNotification(entry, alertAgain); - } - } else if (shouldPeek && alertAgain) { - // This notification was updated to be a heads-up, show it! - mHeadsUpManager.showNotification(entry); + if (isHeadsUp) { + mDozeServiceHost.fireNotificationHeadsUp(); } } @@ -2821,14 +1971,6 @@ public class StatusBar extends SystemUI implements DemoMode, } } - public boolean isHeadsUp(String key) { - return mHeadsUpManager.isHeadsUp(key); - } - - protected boolean isSnoozedPackage(StatusBarNotification sbn) { - return mHeadsUpManager.isSnoozed(sbn.getPackageName()); - } - public boolean isKeyguardCurrentlySecure() { return !mUnlockMethodCache.canSkipBouncer(); } @@ -2846,30 +1988,16 @@ public class StatusBar extends SystemUI implements DemoMode, } if (!isExpanded) { - removeRemoteInputEntriesKeptUntilCollapsed(); + mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed(); } } - private void removeRemoteInputEntriesKeptUntilCollapsed() { - for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) { - Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i); - mRemoteInputController.removeRemoteInput(entry, null); - removeNotification(entry.key, mLatestRankingMap); - } - mRemoteInputEntriesToRemoveOnCollapse.clear(); - } - public NotificationStackScrollLayout getNotificationScrollLayout() { return mStackScroller; } public boolean isPulsing() { - return mDozeScrimController.isPulsing(); - } - - @Override - public void onReorderingAllowed() { - updateNotifications(); + return mDozeScrimController != null && mDozeScrimController.isPulsing(); } public boolean isLaunchTransitionFadingAway() { @@ -2889,7 +2017,7 @@ public class StatusBar extends SystemUI implements DemoMode, OverlayInfo themeInfo = null; try { themeInfo = mOverlayManager.getOverlayInfo("com.android.systemui.theme.dark", - mCurrentUserId); + mLockscreenUserManager.getCurrentUserId()); } catch (RemoteException e) { e.printStackTrace(); } @@ -2935,6 +2063,12 @@ public class StatusBar extends SystemUI implements DemoMode, } } + public void onLaunchAnimationCancelled() { + if (!isCollapsing()) { + onClosingFinished(); + } + } + /** * All changes to the status bar and notifications funnel through here and are batched. */ @@ -2966,9 +2100,8 @@ public class StatusBar extends SystemUI implements DemoMode, } public void maybeEscalateHeadsUp() { - Collection<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getAllEntries(); - for (HeadsUpManager.HeadsUpEntry entry : entries) { - final StatusBarNotification sbn = entry.entry.notification; + mHeadsUpManager.getAllEntries().forEach(entry -> { + final StatusBarNotification sbn = entry.notification; final Notification notification = sbn.getNotification(); if (notification.fullScreenIntent != null) { if (DEBUG) { @@ -2978,11 +2111,11 @@ public class StatusBar extends SystemUI implements DemoMode, EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION, sbn.getKey()); notification.fullScreenIntent.send(); - entry.entry.notifyFullScreenIntentLaunched(); + entry.notifyFullScreenIntentLaunched(); } catch (PendingIntent.CanceledException e) { } } - } + }); mHeadsUpManager.releaseAllImmediately(); } @@ -3017,8 +2150,25 @@ public class StatusBar extends SystemUI implements DemoMode, } + @Override + public void showPinningEnterExitToast(boolean entering) { + if (entering) { + mScreenPinningNotify.showPinningStartToast(); + } else { + mScreenPinningNotify.showPinningExitToast(); + } + } + + @Override + public void showPinningEscapeToast() { + mScreenPinningNotify.showEscapeToast(getNavigationBarView() == null + || getNavigationBarView().isRecentsButtonVisible()); + } + boolean panelsEnabled() { - return (mDisabled1 & StatusBarManager.DISABLE_EXPAND) == 0 && !ONLY_CORE_APPS; + return (mDisabled1 & StatusBarManager.DISABLE_EXPAND) == 0 + && (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0 + && !ONLY_CORE_APPS; } void makeExpandedVisible(boolean force) { @@ -3034,7 +2184,6 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarWindowManager.setPanelVisible(true); visibilityChanged(true); - mWaitingForKeyguardExit = false; recomputeDisableFlags(!force /* animate */); setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true); } @@ -3043,23 +2192,15 @@ public class StatusBar extends SystemUI implements DemoMode, animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); } - private final Runnable mAnimateCollapsePanels = new Runnable() { - @Override - public void run() { - animateCollapsePanels(); - } - }; + private final Runnable mAnimateCollapsePanels = this::animateCollapsePanels; public void postAnimateCollapsePanels() { mHandler.post(mAnimateCollapsePanels); } public void postAnimateForceCollapsePanels() { - mHandler.post(new Runnable() { - @Override - public void run() { - animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); - } + mHandler.post(() -> { + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); }); } @@ -3109,6 +2250,9 @@ public class StatusBar extends SystemUI implements DemoMode, } } + // TODO(b/62444020): remove when this bug is fixed + Log.v(TAG, "mStatusBarWindow: " + mStatusBarWindow + " canPanelBeCollapsed(): " + + mNotificationPanel.canPanelBeCollapsed()); if (mStatusBarWindow != null && mNotificationPanel.canPanelBeCollapsed()) { // release focus immediately to kick off focus change transition mStatusBarWindowManager.setStatusBarFocusable(false); @@ -3187,8 +2331,8 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarWindowManager.setForceStatusBarVisible(false); // Close any guts that might be visible - closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, true /* removeControls */, - -1 /* x */, -1 /* y */, true /* resetMenu */); + mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, + true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */); runPostCollapseRunnables(); setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); @@ -3214,7 +2358,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (SPEW) { Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled1=" - + mDisabled1 + " mDisabled2=" + mDisabled2 + " mTracking=" + mTracking); + + mDisabled1 + " mDisabled2=" + mDisabled2); } else if (CHATTY) { if (event.getAction() != MotionEvent.ACTION_MOVE) { Log.d(TAG, String.format( @@ -3299,10 +2443,8 @@ public class StatusBar extends SystemUI implements DemoMode, sbModeChanged = sbMode != -1; if (sbModeChanged && sbMode != mStatusBarMode) { - if (sbMode != mStatusBarMode) { - mStatusBarMode = sbMode; - checkBarModes(); - } + mStatusBarMode = sbMode; + checkBarModes(); touchAutoHide(); } @@ -3318,6 +2460,29 @@ public class StatusBar extends SystemUI implements DemoMode, mask, fullscreenStackBounds, dockedStackBounds, sbModeChanged, mStatusBarMode); } + @Override + public void showWirelessChargingAnimation(int batteryLevel) { + if (mDozing || mKeyguardManager.isKeyguardLocked()) { + // on ambient or lockscreen, hide notification panel + WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null, + batteryLevel, new WirelessChargingAnimation.Callback() { + @Override + public void onAnimationStarting() { + CrossFadeHelper.fadeOut(mNotificationPanel, 1); + } + + @Override + public void onAnimationEnded() { + CrossFadeHelper.fadeIn(mNotificationPanel); + } + }).show(); + } else { + // workspace + WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null, + batteryLevel, null).show(); + } + } + void touchAutoHide() { // update transient bar autohide if (mStatusBarMode == MODE_SEMI_TRANSPARENT || (mNavigationBar != null @@ -3371,12 +2536,8 @@ public class StatusBar extends SystemUI implements DemoMode, } void checkBarMode(int mode, int windowState, BarTransitions transitions) { - final boolean powerSave = mBatteryController.isPowerSave(); final boolean anim = !mNoAnimationOnNextBarModeChange && mDeviceInteractive - && windowState != WINDOW_STATE_HIDDEN && !powerSave; - if (powerSave && getBarState() == StatusBarState.SHADE) { - mode = MODE_WARNING; - } + && windowState != WINDOW_STATE_HIDDEN; transitions.transitionTo(mode, anim); } @@ -3389,12 +2550,7 @@ public class StatusBar extends SystemUI implements DemoMode, } } - private final Runnable mCheckBarModes = new Runnable() { - @Override - public void run() { - checkBarModes(); - } - }; + private final Runnable mCheckBarModes = this::checkBarModes; public void setInteracting(int barWindow, boolean interacting) { final boolean changing = ((mInteractingWindows & barWindow) != 0) != interacting; @@ -3453,23 +2609,16 @@ public class StatusBar extends SystemUI implements DemoMode, } } - void checkUserAutohide(View v, MotionEvent event) { + void checkUserAutohide(MotionEvent event) { if ((mSystemUiVisibility & STATUS_OR_NAV_TRANSIENT) != 0 // a transient bar is revealed && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar && event.getX() == 0 && event.getY() == 0 // a touch outside both bars - && !mRemoteInputController.isRemoteInputActive()) { // not due to typing in IME + && !mRemoteInputManager.getController() + .isRemoteInputActive()) { // not due to typing in IME userAutohide(); } } - private void checkRemoteInputOutside(MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar - && event.getX() == 0 && event.getY() == 0 // a touch outside both bars - && mRemoteInputController.isRemoteInputActive()) { - mRemoteInputController.closeRemoteInputs(); - } - } - private void userAutohide() { cancelAutohide(); mHandler.postDelayed(mAutohide, 350); // longer than app gesture -> flag clear @@ -3520,23 +2669,13 @@ public class StatusBar extends SystemUI implements DemoMode, public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { synchronized (mQueueLock) { pw.println("Current Status Bar state:"); - pw.println(" mExpandedVisible=" + mExpandedVisible - + ", mTrackingPosition=" + mTrackingPosition); - pw.println(" mTracking=" + mTracking); + pw.println(" mExpandedVisible=" + mExpandedVisible); pw.println(" mDisplayMetrics=" + mDisplayMetrics); pw.println(" mStackScroller: " + viewInfo(mStackScroller)); pw.println(" mStackScroller: " + viewInfo(mStackScroller) + " scroll " + mStackScroller.getScrollX() + "," + mStackScroller.getScrollY()); } - pw.print(" mPendingNotifications="); - if (mPendingNotifications.size() == 0) { - pw.println("null"); - } else { - for (Entry entry : mPendingNotifications.values()) { - pw.println(entry.notification); - } - } pw.print(" mInteractingWindows="); pw.println(mInteractingWindows); pw.print(" mStatusBarWindowState="); @@ -3545,31 +2684,22 @@ public class StatusBar extends SystemUI implements DemoMode, pw.println(BarTransitions.modeToString(mStatusBarMode)); pw.print(" mDozing="); pw.println(mDozing); pw.print(" mZenMode="); - pw.println(Settings.Global.zenModeToString(mZenMode)); - pw.print(" mUseHeadsUp="); - pw.println(mUseHeadsUp); - pw.print(" mKeyToRemoveOnGutsClosed="); - pw.println(mKeyToRemoveOnGutsClosed); + pw.println(Settings.Global.zenModeToString(Settings.Global.getInt( + mContext.getContentResolver(), Settings.Global.ZEN_MODE, + Settings.Global.ZEN_MODE_OFF))); + if (mStatusBarView != null) { dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions()); } - - pw.print(" mMediaSessionManager="); - pw.println(mMediaSessionManager); - pw.print(" mMediaNotificationKey="); - pw.println(mMediaNotificationKey); - pw.print(" mMediaController="); - pw.print(mMediaController); - if (mMediaController != null) { - pw.print(" state=" + mMediaController.getPlaybackState()); + pw.println(" StatusBarWindowView: "); + if (mStatusBarWindow != null) { + mStatusBarWindow.dump(fd, pw, args); } - pw.println(); - pw.print(" mMediaMetadata="); - pw.print(mMediaMetadata); - if (mMediaMetadata != null) { - pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE)); + + pw.println(" mMediaManager: "); + if (mMediaManager != null) { + mMediaManager.dump(fd, pw, args); } - pw.println(); pw.println(" Panels: "); if (mNotificationPanel != null) { @@ -3598,28 +2728,28 @@ public class StatusBar extends SystemUI implements DemoMode, mFingerprintUnlockController.dump(pw); } + if (mKeyguardIndicationController != null) { + mKeyguardIndicationController.dump(fd, pw, args); + } + if (mScrimController != null) { - mScrimController.dump(pw); + mScrimController.dump(fd, pw, args); } if (DUMPTRUCK) { - synchronized (mNotificationData) { - mNotificationData.dump(pw, " "); + synchronized (mEntryManager.getNotificationData()) { + mEntryManager.getNotificationData().dump(pw, " "); } if (false) { pw.println("see the logcat for a dump of the views we have created."); // must happen on ui thread - mHandler.post(new Runnable() { - @Override - public void run() { - mStatusBarView.getLocationOnScreen(mAbsPos); - Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] - + ") " + mStatusBarView.getWidth() + "x" - + getStatusBarHeight()); - mStatusBarView.debug(); - } - }); + mHandler.post(() -> { + mStatusBarView.getLocationOnScreen(mAbsPos); + Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] + + ") " + mStatusBarView.getWidth() + "x" + getStatusBarHeight()); + mStatusBarView.debug(); + }); } } @@ -3668,7 +2798,21 @@ public class StatusBar extends SystemUI implements DemoMode, private void addStatusBarWindow() { makeStatusBarView(); mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class); - mRemoteInputController = new RemoteInputController(mHeadsUpManager); + mRemoteInputManager.setUpWithPresenter(this, mEntryManager, this, + new RemoteInputController.Delegate() { + public void setRemoteInputActive(NotificationData.Entry entry, + boolean remoteInputActive) { + mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); + } + public void lockScrollTo(NotificationData.Entry entry) { + mStackScroller.lockScrollTo(entry.row); + } + public void requestDisallowLongPressAndDismiss() { + mStackScroller.requestDisallowLongPress(); + mStackScroller.requestDisallowDismiss(); + } + }); + mRemoteInputManager.getController().addCallback(mStatusBarWindowManager); mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight()); } @@ -3698,50 +2842,45 @@ public class StatusBar extends SystemUI implements DemoMode, if (onlyProvisioned && !isDeviceProvisioned()) return; final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity( - mContext, intent, mCurrentUserId); - Runnable runnable = new Runnable() { - @Override - public void run() { - mAssistManager.hideAssist(); - intent.setFlags( - Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - int result = ActivityManager.START_CANCELED; - ActivityOptions options = new ActivityOptions(getActivityOptions()); - options.setDisallowEnterPictureInPictureWhileLaunching( - disallowEnterPictureInPictureWhileLaunching); - if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) { - // Normally an activity will set it's requested rotation - // animation on its window. However when launching an activity - // causes the orientation to change this is too late. In these cases - // the default animation is used. This doesn't look good for - // the camera (as it rotates the camera contents out of sync - // with physical reality). So, we ask the WindowManager to - // force the crossfade animation if an orientation change - // happens to occur during the launch. - options.setRotationAnimationHint( - WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS); - } - try { - result = ActivityManager.getService().startActivityAsUser( - null, mContext.getBasePackageName(), - intent, - intent.resolveTypeIfNeeded(mContext.getContentResolver()), - null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, - options.toBundle(), UserHandle.CURRENT.getIdentifier()); - } catch (RemoteException e) { - Log.w(TAG, "Unable to start activity", e); - } - if (callback != null) { - callback.onActivityStarted(result); - } + mContext, intent, mLockscreenUserManager.getCurrentUserId()); + Runnable runnable = () -> { + mAssistManager.hideAssist(); + intent.setFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + int result = ActivityManager.START_CANCELED; + ActivityOptions options = new ActivityOptions(getActivityOptions( + null /* sourceNotification */)); + options.setDisallowEnterPictureInPictureWhileLaunching( + disallowEnterPictureInPictureWhileLaunching); + if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) { + // Normally an activity will set it's requested rotation + // animation on its window. However when launching an activity + // causes the orientation to change this is too late. In these cases + // the default animation is used. This doesn't look good for + // the camera (as it rotates the camera contents out of sync + // with physical reality). So, we ask the WindowManager to + // force the crossfade animation if an orientation change + // happens to occur during the launch. + options.setRotationAnimationHint( + WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS); + } + try { + result = ActivityManager.getService().startActivityAsUser( + null, mContext.getBasePackageName(), + intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, + options.toBundle(), UserHandle.CURRENT.getIdentifier()); + } catch (RemoteException e) { + Log.w(TAG, "Unable to start activity", e); + } + if (callback != null) { + callback.onActivityStarted(result); } }; - Runnable cancelRunnable = new Runnable() { - @Override - public void run() { - if (callback != null) { - callback.onActivityStarted(ActivityManager.START_CANCELED); - } + Runnable cancelRunnable = () -> { + if (callback != null) { + callback.onActivityStarted(ActivityManager.START_CANCELED); } }; executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShade, @@ -3786,17 +2925,17 @@ public class StatusBar extends SystemUI implements DemoMode, }, cancelAction, afterKeyguardGone); } - private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.v(TAG, "onReceive: " + intent); String action = intent.getAction(); if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { KeyboardShortcuts.dismiss(); - if (mRemoteInputController != null) { - mRemoteInputController.closeRemoteInputs(); + if (mRemoteInputManager.getController() != null) { + mRemoteInputManager.getController().closeRemoteInputs(); } - if (isCurrentProfile(getSendingUserId())) { + if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) { int flags = CommandQueue.FLAG_EXCLUDE_NONE; String reason = intent.getStringExtra("reason"); if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) { @@ -3815,7 +2954,7 @@ public class StatusBar extends SystemUI implements DemoMode, } }; - private BroadcastReceiver mDemoReceiver = new BroadcastReceiver() { + private final BroadcastReceiver mDemoReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.v(TAG, "onReceive: " + intent); @@ -3841,7 +2980,8 @@ public class StatusBar extends SystemUI implements DemoMode, }; public void resetUserExpandedStates() { - ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); + ArrayList<Entry> activeNotifications = mEntryManager.getNotificationData() + .getActiveNotifications(); final int notificationCount = activeNotifications.size(); for (int i = 0; i < notificationCount; i++) { NotificationData.Entry entry = activeNotifications.get(i); @@ -3884,27 +3024,40 @@ public class StatusBar extends SystemUI implements DemoMode, Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration()); } - updateRowStates(); + mViewHierarchyManager.updateRowStates(); mScreenPinningRequest.onConfigurationChanged(); } - public void userSwitched(int newUserId) { + @Override + public void onUserSwitched(int newUserId) { // Begin old BaseStatusBar.userSwitched setHeadsUpUser(newUserId); // End old BaseStatusBar.userSwitched if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId); animateCollapsePanels(); updatePublicMode(); - mNotificationData.filterAndSort(); + mEntryManager.getNotificationData().filterAndSort(); if (mReinflateNotificationsOnUserSwitched) { - updateNotificationsOnDensityOrFontScaleChanged(); + mEntryManager.updateNotificationsOnDensityOrFontScaleChanged(); mReinflateNotificationsOnUserSwitched = false; } - updateNotificationShade(); - clearCurrentMediaNotification(); + updateNotificationViews(); + mMediaManager.clearCurrentMediaNotification(); setLockscreenUser(newUserId); } + @Override + public NotificationLockscreenUserManager getNotificationLockscreenUserManager() { + return mLockscreenUserManager; + } + + @Override + public void onBindRow(Entry entry, PackageManager pmUser, + StatusBarNotification sbn, ExpandableNotificationRow row) { + row.setAboveShelfChangedListener(mAboveShelfObserver); + row.setSecureStateProvider(this::isKeyguardCurrentlySecure); + } + protected void setLockscreenUser(int newUserId) { mLockscreenWallpaper.setCurrentUser(newUserId); mScrimController.setCurrentUser(newUserId); @@ -3954,9 +3107,9 @@ public class StatusBar extends SystemUI implements DemoMode, protected void handleVisibleToUserChanged(boolean visibleToUser) { if (visibleToUser) { handleVisibleToUserChangedImpl(visibleToUser); - startNotificationLogging(); + mNotificationLogger.startNotificationLogging(); } else { - stopNotificationLogging(); + mNotificationLogger.stopNotificationLogging(); handleVisibleToUserChangedImpl(visibleToUser); } } @@ -3965,7 +3118,8 @@ public class StatusBar extends SystemUI implements DemoMode, try { // consider the transition from peek to expanded to be a panel open, // but not one that clears notification effects. - int notificationLoad = mNotificationData.getActiveNotifications().size(); + int notificationLoad = mEntryManager.getNotificationData() + .getActiveNotifications().size(); mBarService.onPanelRevealed(false, notificationLoad); } catch (RemoteException ex) { // Won't fail unless the world has ended. @@ -3976,79 +3130,39 @@ public class StatusBar extends SystemUI implements DemoMode, * The LEDs are turned off when the notification panel is shown, even just a little bit. * See also StatusBar.setPanelExpanded for another place where we attempt to do this. */ - // Old BaseStatusBar.handleVisibileToUserChanged private void handleVisibleToUserChangedImpl(boolean visibleToUser) { - try { - if (visibleToUser) { - boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp(); - boolean clearNotificationEffects = - !isPanelFullyCollapsed() && - (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED); - int notificationLoad = mNotificationData.getActiveNotifications().size(); - if (pinnedHeadsUp && isPanelFullyCollapsed()) { - notificationLoad = 1; + if (visibleToUser) { + boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp(); + boolean clearNotificationEffects = + !isPresenterFullyCollapsed() && + (mState == StatusBarState.SHADE + || mState == StatusBarState.SHADE_LOCKED); + int notificationLoad = mEntryManager.getNotificationData().getActiveNotifications() + .size(); + if (pinnedHeadsUp && isPresenterFullyCollapsed()) { + notificationLoad = 1; + } + final int finalNotificationLoad = notificationLoad; + mUiOffloadThread.submit(() -> { + try { + mBarService.onPanelRevealed(clearNotificationEffects, + finalNotificationLoad); + } catch (RemoteException ex) { + // Won't fail unless the world has ended. } - mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad); - } else { - mBarService.onPanelHidden(); - } - } catch (RemoteException ex) { - // Won't fail unless the world has ended. - } - } - - private void stopNotificationLogging() { - // Report all notifications as invisible and turn down the - // reporter. - if (!mCurrentlyVisibleNotifications.isEmpty()) { - logNotificationVisibilityChanges(Collections.<NotificationVisibility>emptyList(), - mCurrentlyVisibleNotifications); - recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); - } - mHandler.removeCallbacks(mVisibilityReporter); - mStackScroller.setChildLocationsChangedListener(null); - } - - private void startNotificationLogging() { - mStackScroller.setChildLocationsChangedListener(mNotificationLocationsChangedListener); - // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't - // cause the scroller to emit child location events. Hence generate - // one ourselves to guarantee that we're reporting visible - // notifications. - // (Note that in cases where the scroller does emit events, this - // additional event doesn't break anything.) - mNotificationLocationsChangedListener.onChildLocationsChanged(mStackScroller); - } - - private void logNotificationVisibilityChanges( - Collection<NotificationVisibility> newlyVisible, - Collection<NotificationVisibility> noLongerVisible) { - if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) { - return; - } - NotificationVisibility[] newlyVisibleAr = - newlyVisible.toArray(new NotificationVisibility[newlyVisible.size()]); - NotificationVisibility[] noLongerVisibleAr = - noLongerVisible.toArray(new NotificationVisibility[noLongerVisible.size()]); - try { - mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr); - } catch (RemoteException e) { - // Ignore. + }); + } else { + mUiOffloadThread.submit(() -> { + try { + mBarService.onPanelHidden(); + } catch (RemoteException ex) { + // Won't fail unless the world has ended. + } + }); } - final int N = newlyVisible.size(); - if (N > 0) { - String[] newlyVisibleKeyAr = new String[N]; - for (int i = 0; i < N; i++) { - newlyVisibleKeyAr[i] = newlyVisibleAr[i].key; - } - - setNotificationsShown(newlyVisibleKeyAr); - } } - // State logging - private void logStateToEventlog() { boolean isShowing = mStatusBarKeyguardViewManager.isShowing(); boolean isOccluded = mStatusBarKeyguardViewManager.isOccluded(); @@ -4109,7 +3223,7 @@ public class StatusBar extends SystemUI implements DemoMode, vib.vibrate(250, VIBRATION_ATTRIBUTES); } - Runnable mStartTracing = new Runnable() { + final Runnable mStartTracing = new Runnable() { @Override public void run() { vibrate(); @@ -4120,13 +3234,10 @@ public class StatusBar extends SystemUI implements DemoMode, } }; - Runnable mStopTracing = new Runnable() { - @Override - public void run() { - android.os.Debug.stopMethodTracing(); - Log.d(TAG, "stopTracing"); - vibrate(); - } + final Runnable mStopTracing = () -> { + android.os.Debug.stopMethodTracing(); + Log.d(TAG, "stopTracing"); + vibrate(); }; @Override @@ -4153,49 +3264,16 @@ public class StatusBar extends SystemUI implements DemoMode, startActivityDismissingKeyguard(intent, onlyProvisioned, true /* dismissShade */); } - private static class FastColorDrawable extends Drawable { - private final int mColor; - - public FastColorDrawable(int color) { - mColor = 0xff000000 | color; - } - - @Override - public void draw(Canvas canvas) { - canvas.drawColor(mColor, PorterDuff.Mode.SRC); - } - - @Override - public void setAlpha(int alpha) { - } - - @Override - public void setColorFilter(ColorFilter colorFilter) { - } - - @Override - public int getOpacity() { - return PixelFormat.OPAQUE; - } - - @Override - public void setBounds(int left, int top, int right, int bottom) { - } - - @Override - public void setBounds(Rect bounds) { - } - } - public void destroy() { // Begin old BaseStatusBar.destroy(). - mContext.unregisterReceiver(mBaseBroadcastReceiver); + mContext.unregisterReceiver(mBannerActionBroadcastReceiver); + mLockscreenUserManager.destroy(); try { mNotificationListener.unregisterAsSystemService(); } catch (RemoteException e) { // Ignore. } - mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener); + mEntryManager.destroy(); // End old BaseStatusBar.destroy(). if (mStatusBarWindow != null) { mWindowManager.removeViewImmediate(mStatusBarWindow); @@ -4279,6 +3357,9 @@ public class StatusBar extends SystemUI implements DemoMode, } } } + if (modeChange || command.equals(COMMAND_OPERATOR)) { + dispatchDemoCommandToView(command, args, R.id.operator_name); + } } private void dispatchDemoCommandToView(String command, Bundle args, int id) { @@ -4296,7 +3377,8 @@ public class StatusBar extends SystemUI implements DemoMode, return mState; } - public boolean isPanelFullyCollapsed() { + @Override + public boolean isPresenterFullyCollapsed() { return mNotificationPanel.isFullyCollapsed(); } @@ -4376,12 +3458,11 @@ public class StatusBar extends SystemUI implements DemoMode, releaseGestureWakeLock(); runLaunchTransitionEndRunnable(); mLaunchTransitionFadingAway = false; - mScrimController.forceHideScrims(false /* hide */, false /* animated */); updateMediaMetaData(true /* metaDataChanged */, true); } public boolean isCollapsing() { - return mNotificationPanel.isCollapsing(); + return mNotificationPanel.isCollapsing() || mActivityLaunchAnimator.isAnimationPending(); } public void addPostCollapseAction(Runnable r) { @@ -4404,31 +3485,23 @@ public class StatusBar extends SystemUI implements DemoMode, Runnable endRunnable) { mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); mLaunchTransitionEndRunnable = endRunnable; - Runnable hideRunnable = new Runnable() { - @Override - public void run() { - mLaunchTransitionFadingAway = true; - if (beforeFading != null) { - beforeFading.run(); - } - mScrimController.forceHideScrims(true /* hide */, false /* animated */); - updateMediaMetaData(false, true); - mNotificationPanel.setAlpha(1); - mStackScroller.setParentNotFullyVisible(true); - mNotificationPanel.animate() - .alpha(0) - .setStartDelay(FADE_KEYGUARD_START_DELAY) - .setDuration(FADE_KEYGUARD_DURATION) - .withLayer() - .withEndAction(new Runnable() { - @Override - public void run() { - onLaunchTransitionFadingEnded(); - } - }); - mCommandQueue.appTransitionStarting(SystemClock.uptimeMillis(), - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true); - } + Runnable hideRunnable = () -> { + mLaunchTransitionFadingAway = true; + if (beforeFading != null) { + beforeFading.run(); + } + updateScrimController(); + updateMediaMetaData(false, true); + mNotificationPanel.setAlpha(1); + mStackScroller.setParentNotFullyVisible(true); + mNotificationPanel.animate() + .alpha(0) + .setStartDelay(FADE_KEYGUARD_START_DELAY) + .setDuration(FADE_KEYGUARD_DURATION) + .withLayer() + .withEndAction(this::onLaunchTransitionFadingEnded); + mCommandQueue.appTransitionStarting(SystemClock.uptimeMillis(), + LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true); }; if (mNotificationPanel.isLaunchTransitionRunning()) { mNotificationPanel.setLaunchTransitionEndRunnable(hideRunnable); @@ -4448,14 +3521,16 @@ public class StatusBar extends SystemUI implements DemoMode, .setStartDelay(0) .setDuration(FADE_KEYGUARD_DURATION_PULSING) .setInterpolator(ScrimController.KEYGUARD_FADE_OUT_INTERPOLATOR) - .start(); + .withEndAction(()-> { + hideKeyguard(); + mStatusBarKeyguardViewManager.onKeyguardFadedAway(); + }).start(); } /** * Plays the animation when an activity that was occluding Keyguard goes away. */ public void animateKeyguardUnoccluding() { - mScrimController.animateKeyguardUnoccluding(500); mNotificationPanel.setExpandedFraction(0f); animateExpandNotificationsPanel(); } @@ -4557,7 +3632,6 @@ public class StatusBar extends SystemUI implements DemoMode, // Treat Keyguard exit animation as an app transition to achieve nice transition for status // bar. - mKeyguardGoingAway = true; mKeyguardMonitor.notifyKeyguardGoingAway(true); mCommandQueue.appTransitionPending(true); } @@ -4566,14 +3640,13 @@ public class StatusBar extends SystemUI implements DemoMode, * Notifies the status bar the Keyguard is fading away with the specified timings. * * @param startTime the start time of the animations in uptime millis - * @param delay the precalculated animation delay in miliseconds + * @param delay the precalculated animation delay in milliseconds * @param fadeoutDuration the duration of the exit animation, in milliseconds */ public void setKeyguardFadingAway(long startTime, long delay, long fadeoutDuration) { mKeyguardFadingAway = true; mKeyguardFadingAwayDelay = delay; mKeyguardFadingAwayDuration = fadeoutDuration; - mWaitingForKeyguardExit = false; mCommandQueue.appTransitionStarting(startTime + fadeoutDuration - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true); @@ -4593,26 +3666,24 @@ public class StatusBar extends SystemUI implements DemoMode, */ public void finishKeyguardFadingAway() { mKeyguardFadingAway = false; - mKeyguardGoingAway = false; mKeyguardMonitor.notifyKeyguardDoneFading(); } - public void stopWaitingForKeyguardExit() { - mWaitingForKeyguardExit = false; - } - + // TODO: Move this to NotificationLockscreenUserManager. private void updatePublicMode() { final boolean showingKeyguard = mStatusBarKeyguardViewManager.isShowing(); final boolean devicePublic = showingKeyguard - && mStatusBarKeyguardViewManager.isSecure(mCurrentUserId); + && mStatusBarKeyguardViewManager.isSecure( + mLockscreenUserManager.getCurrentUserId()); // Look for public mode users. Users are considered public in either case of: // - device keyguard is shown in secure mode; // - profile is locked with a work challenge. - for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) { - final int userId = mCurrentProfiles.valueAt(i).id; + SparseArray<UserInfo> currentProfiles = mLockscreenUserManager.getCurrentProfiles(); + for (int i = currentProfiles.size() - 1; i >= 0; i--) { + final int userId = currentProfiles.valueAt(i).id; boolean isProfilePublic = devicePublic; - if (!devicePublic && userId != mCurrentUserId) { + if (!devicePublic && userId != mLockscreenUserManager.getCurrentUserId()) { // We can't rely on KeyguardManager#isDeviceLocked() for unified profile challenge // due to a race condition where this code could be called before // TrustManagerService updates its internal records, resulting in an incorrect @@ -4622,7 +3693,7 @@ public class StatusBar extends SystemUI implements DemoMode, isProfilePublic = mKeyguardManager.isDeviceLocked(userId); } } - setLockscreenPublicMode(isProfilePublic, userId); + mLockscreenUserManager.setLockscreenPublicMode(isProfilePublic, userId); } } @@ -4650,18 +3721,14 @@ public class StatusBar extends SystemUI implements DemoMode, mAmbientIndicationContainer.setVisibility(View.INVISIBLE); } } - if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { - mScrimController.setKeyguardShowing(true); - } else { - mScrimController.setKeyguardShowing(false); - } mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade); updateTheme(); updateDozingState(); updatePublicMode(); updateStackScrollerState(goingToFullShade, fromShadeLocked); - updateNotifications(); + mEntryManager.updateNotifications(); checkBarModes(); + updateScrimController(); updateMediaMetaData(false, mState != StatusBarState.KEYGUARD); mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(), mUnlockMethodCache.isMethodSecure(), @@ -4681,12 +3748,14 @@ public class StatusBar extends SystemUI implements DemoMode, final boolean useDarkTheme = systemColors != null && (systemColors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0; if (isUsingDarkTheme() != useDarkTheme) { - try { - mOverlayManager.setEnabled("com.android.systemui.theme.dark", - useDarkTheme, mCurrentUserId); - } catch (RemoteException e) { - Log.w(TAG, "Can't change theme", e); - } + mUiOffloadThread.submit(() -> { + try { + mOverlayManager.setEnabled("com.android.systemui.theme.dark", + useDarkTheme, mLockscreenUserManager.getCurrentUserId()); + } catch (RemoteException e) { + Log.w(TAG, "Can't change theme", e); + } + }); } // Lock wallpaper defines the color of the majority of the views, hence we'll use it @@ -4697,7 +3766,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (mContext.getThemeResId() != themeResId) { mContext.setTheme(themeResId); if (inflated) { - reinflateViews(); + onThemeChanged(); } } @@ -4720,22 +3789,22 @@ public class StatusBar extends SystemUI implements DemoMode, private void updateDozingState() { Trace.traceCounter(Trace.TRACE_TAG_APP, "dozing", mDozing ? 1 : 0); Trace.beginSection("StatusBar#updateDozingState"); - boolean animate = !mDozing && mDozeServiceHost.shouldAnimateWakeup(); + boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup()) + || (mDozing && mDozeServiceHost.shouldAnimateScreenOff()); mNotificationPanel.setDozing(mDozing, animate); mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation); - mScrimController.setDozing(mDozing); + mDozeScrimController.setDozing(mDozing); mKeyguardIndicationController.setDozing(mDozing); mNotificationPanel.setDark(mDozing, animate); updateQsExpansionEnabled(); - mDozeScrimController.setDozing(mDozing, animate); - updateRowStates(); + mViewHierarchyManager.updateRowStates(); Trace.endSection(); } public void updateStackScrollerState(boolean goingToFullShade, boolean fromShadeLocked) { if (mStackScroller == null) return; boolean onKeyguard = mState == StatusBarState.KEYGUARD; - boolean publicMode = isAnyProfilePublicMode(); + boolean publicMode = mLockscreenUserManager.isAnyProfilePublicMode(); mStackScroller.setHideSensitive(publicMode, goingToFullShade); mStackScroller.setDimmed(onKeyguard, fromShadeLocked /* animate */); mStackScroller.setExpandingEnabled(!onKeyguard); @@ -4814,7 +3883,6 @@ public class StatusBar extends SystemUI implements DemoMode, } protected void showBouncer() { - mWaitingForKeyguardExit = mStatusBarKeyguardViewManager.isShowing(); mStatusBarKeyguardViewManager.dismiss(); } @@ -4827,11 +3895,12 @@ public class StatusBar extends SystemUI implements DemoMode, private void instantCollapseNotificationPanel() { mNotificationPanel.instantCollapse(); + runPostCollapseRunnables(); } @Override public void onActivated(ActivatableNotificationView view) { - onActivated((View)view); + onActivated((View) view); mStackScroller.setActivatedChild(view); } @@ -4859,7 +3928,7 @@ public class StatusBar extends SystemUI implements DemoMode, clearNotificationEffects(); } if (state == StatusBarState.KEYGUARD) { - removeRemoteInputEntriesKeptUntilCollapsed(); + mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed(); maybeEscalateHeadsUp(); } mState = state; @@ -4893,7 +3962,7 @@ public class StatusBar extends SystemUI implements DemoMode, public void onClosingFinished() { runPostCollapseRunnables(); - if (!isPanelFullyCollapsed()) { + if (!isPresenterFullyCollapsed()) { // if we set it not to be focusable when collapsing, we have to undo it when we aborted // the closing mStatusBarWindowManager.setStatusBarFocusable(true); @@ -4933,7 +4002,8 @@ public class StatusBar extends SystemUI implements DemoMode, } } - protected int getMaxKeyguardNotifications(boolean recompute) { + @Override + public int getMaxNotificationsWhileLocked(boolean recompute) { if (recompute) { mMaxKeyguardNotifications = Math.max(1, mNotificationPanel.computeMaxKeyguardNotifications( @@ -4943,8 +4013,8 @@ public class StatusBar extends SystemUI implements DemoMode, return mMaxKeyguardNotifications; } - public int getMaxKeyguardNotifications() { - return getMaxKeyguardNotifications(false /* recompute */); + public int getMaxNotificationsWhileLocked() { + return getMaxNotificationsWhileLocked(false /* recompute */); } // TODO: Figure out way to remove these. @@ -5004,7 +4074,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onTouchSlopExceeded() { - mStackScroller.removeLongPressCallback(); + mStackScroller.cancelLongPress(); mStackScroller.checkSnoozeLeavebehind(); } @@ -5026,7 +4096,11 @@ public class StatusBar extends SystemUI implements DemoMode, * @param expandView The view to expand after going to the shade. */ public void goToLockedShade(View expandView) { - int userId = mCurrentUserId; + if ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) { + return; + } + + int userId = mLockscreenUserManager.getCurrentUserId(); ExpandableNotificationRow row = null; if (expandView instanceof ExpandableNotificationRow) { row = (ExpandableNotificationRow) expandView; @@ -5038,9 +4112,11 @@ public class StatusBar extends SystemUI implements DemoMode, userId = row.getStatusBarNotification().getUserId(); } } - boolean fullShadeNeedsBouncer = !userAllowsPrivateNotificationsInPublic(mCurrentUserId) - || !mShowLockscreenNotifications || mFalsingManager.shouldEnforceBouncer(); - if (isLockscreenPublicMode(userId) && fullShadeNeedsBouncer) { + boolean fullShadeNeedsBouncer = !mLockscreenUserManager. + userAllowsPrivateNotificationsInPublic(mLockscreenUserManager.getCurrentUserId()) + || !mLockscreenUserManager.shouldShowLockscreenNotifications() + || mFalsingManager.shouldEnforceBouncer(); + if (mLockscreenUserManager.isLockscreenPublicMode(userId) && fullShadeNeedsBouncer) { mLeaveOpenOnKeyguardHide = true; showBouncerIfKeyguard(); mDraggedDownRow = row; @@ -5057,13 +4133,15 @@ public class StatusBar extends SystemUI implements DemoMode, dismissKeyguardThenExecute(dismissAction, true /* afterKeyguardGone */); } - protected void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) { + @Override + public void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) { mLeaveOpenOnKeyguardHide = true; showBouncer(); mPendingRemoteInputView = clicked; } - protected void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row, + @Override + public void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row, View clickedView) { if (isKeyguardShowing()) { onLockedRemoteInput(row, clickedView); @@ -5073,6 +4151,47 @@ public class StatusBar extends SystemUI implements DemoMode, } } + @Override + public boolean shouldHandleRemoteInput(View view, PendingIntent pendingIntent) { + // Skip remote input as doing so will expand the notification shade. + return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0; + } + + @Override + public boolean handleRemoteViewClick(View view, PendingIntent pendingIntent, + Intent fillInIntent, NotificationRemoteInputManager.ClickHandler defaultHandler) { + final boolean isActivity = pendingIntent.isActivity(); + if (isActivity) { + final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity( + mContext, pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId()); + dismissKeyguardThenExecute(() -> { + try { + ActivityManager.getService().resumeAppSwitches(); + } catch (RemoteException e) { + } + + boolean handled = defaultHandler.handleClick(); + + // close the shade if it was open + if (handled && !mNotificationPanel.isFullyCollapsed()) { + animateCollapsePanels( + CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */); + visibilityChanged(false); + mAssistManager.hideAssist(); + + // Wait for activity start. + return true; + } else { + return false; + } + + }, afterKeyguardGone); + return true; + } else { + return defaultHandler.handleClick(); + } + } + protected boolean startWorkChallengeIfNecessary(int userId, IntentSender intendSender, String notificationKey) { // Clear pending remote view, as we do not want to trigger pending remote input view when @@ -5109,7 +4228,8 @@ public class StatusBar extends SystemUI implements DemoMode, // End old BaseStatusBar.startWorkChallengeIfNecessary. } - protected void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row, + @Override + public void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row, View clicked) { // Collapse notification and show work challenge animateCollapsePanels(); @@ -5119,65 +4239,48 @@ public class StatusBar extends SystemUI implements DemoMode, mPendingWorkRemoteInputView = clicked; } - private boolean isAnyProfilePublicMode() { - for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) { - if (isLockscreenPublicMode(mCurrentProfiles.valueAt(i).id)) { - return true; - } - } - return false; - } - - protected void onWorkChallengeChanged() { + @Override + public void onWorkChallengeChanged() { updatePublicMode(); - updateNotifications(); - if (mPendingWorkRemoteInputView != null && !isAnyProfilePublicMode()) { + mEntryManager.updateNotifications(); + if (mPendingWorkRemoteInputView != null + && !mLockscreenUserManager.isAnyProfilePublicMode()) { // Expand notification panel and the notification row, then click on remote input view - final Runnable clickPendingViewRunnable = new Runnable() { - @Override - public void run() { - final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView; - if (pendingWorkRemoteInputView == null) { + final Runnable clickPendingViewRunnable = () -> { + final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView; + if (pendingWorkRemoteInputView == null) { + return; + } + + // Climb up the hierarchy until we get to the container for this row. + ViewParent p = pendingWorkRemoteInputView.getParent(); + while (!(p instanceof ExpandableNotificationRow)) { + if (p == null) { return; } + p = p.getParent(); + } - // Climb up the hierarchy until we get to the container for this row. - ViewParent p = pendingWorkRemoteInputView.getParent(); - while (!(p instanceof ExpandableNotificationRow)) { - if (p == null) { - return; + final ExpandableNotificationRow row = (ExpandableNotificationRow) p; + ViewParent viewParent = row.getParent(); + if (viewParent instanceof NotificationStackScrollLayout) { + final NotificationStackScrollLayout scrollLayout = + (NotificationStackScrollLayout) viewParent; + row.makeActionsVisibile(); + row.post(() -> { + final Runnable finishScrollingCallback = () -> { + mPendingWorkRemoteInputView.callOnClick(); + mPendingWorkRemoteInputView = null; + scrollLayout.setFinishScrollingCallback(null); + }; + if (scrollLayout.scrollTo(row)) { + // It scrolls! So call it when it's finished. + scrollLayout.setFinishScrollingCallback(finishScrollingCallback); + } else { + // It does not scroll, so call it now! + finishScrollingCallback.run(); } - p = p.getParent(); - } - - final ExpandableNotificationRow row = (ExpandableNotificationRow) p; - ViewParent viewParent = row.getParent(); - if (viewParent instanceof NotificationStackScrollLayout) { - final NotificationStackScrollLayout scrollLayout = - (NotificationStackScrollLayout) viewParent; - row.makeActionsVisibile(); - row.post(new Runnable() { - @Override - public void run() { - final Runnable finishScrollingCallback = new Runnable() { - @Override - public void run() { - mPendingWorkRemoteInputView.callOnClick(); - mPendingWorkRemoteInputView = null; - scrollLayout.setFinishScrollingCallback(null); - } - }; - if (scrollLayout.scrollTo(row)) { - // It scrolls! So call it when it's finished. - scrollLayout.setFinishScrollingCallback( - finishScrollingCallback); - } else { - // It does not scroll, so call it now! - finishScrollingCallback.run(); - } - } - }); - } + }); } }; mNotificationPanel.getViewTreeObserver().addOnGlobalLayoutListener( @@ -5229,6 +4332,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (mStatusBarView != null) mStatusBarView.setBouncerShowing(bouncerShowing); updateHideIconsForBouncer(true /* animate */); recomputeDisableFlags(true /* animate */); + updateScrimController(); } public void cancelCurrentTouch() { @@ -5240,7 +4344,7 @@ public class StatusBar extends SystemUI implements DemoMode, } } - WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() { + final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() { @Override public void onFinishedGoingToSleep() { mNotificationPanel.onAffordanceLaunchEnded(); @@ -5263,12 +4367,7 @@ public class StatusBar extends SystemUI implements DemoMode, // This gets executed before we will show Keyguard, so post it in order that the state // is correct. - mHandler.post(new Runnable() { - @Override - public void run() { - onCameraLaunchGestureDetected(mLastCameraLaunchSource); - } - }); + mHandler.post(() -> onCameraLaunchGestureDetected(mLastCameraLaunchSource)); } updateIsKeyguard(); } @@ -5285,38 +4384,36 @@ public class StatusBar extends SystemUI implements DemoMode, mStackScroller.setAnimationsEnabled(true); mVisualStabilityManager.setScreenOn(true); mNotificationPanel.setTouchDisabled(false); - - maybePrepareWakeUpFromAod(); - mDozeServiceHost.stopDozing(); updateVisibleToUser(); updateIsKeyguard(); + updateScrimController(); } }; - ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { + final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { @Override public void onScreenTurningOn() { mFalsingManager.onScreenTurningOn(); mNotificationPanel.onScreenTurningOn(); - maybePrepareWakeUpFromAod(); - if (mLaunchCameraOnScreenTurningOn) { mNotificationPanel.launchCamera(false, mLastCameraLaunchSource); mLaunchCameraOnScreenTurningOn = false; } + + updateScrimController(); } @Override public void onScreenTurnedOn() { - mScrimController.wakeUpFromAod(); - mDozeScrimController.onScreenTurnedOn(); + mScrimController.onScreenTurnedOn(); } @Override public void onScreenTurnedOff() { mFalsingManager.onScreenOff(); + mScrimController.onScreenTurnedOff(); // If we pulse in from AOD, we turn the screen off first. However, updatingIsKeyguard // in that case destroys the HeadsUpManager state, so don't do it in that case. if (!isPulsing()) { @@ -5329,13 +4426,6 @@ public class StatusBar extends SystemUI implements DemoMode, return mWakefulnessLifecycle.getWakefulness(); } - private void maybePrepareWakeUpFromAod() { - int wakefulness = mWakefulnessLifecycle.getWakefulness(); - if (mDozing && wakefulness == WAKEFULNESS_WAKING && !isPulsing()) { - mScrimController.prepareWakeUpFromAod(); - } - } - private void vibrateForCameraGesture() { // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep. mVibrator.vibrate(mCameraLaunchGestureVibePattern, -1 /* repeat */); @@ -5364,9 +4454,10 @@ public class StatusBar extends SystemUI implements DemoMode, } public boolean hasActiveNotifications() { - return !mNotificationData.getActiveNotifications().isEmpty(); + return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty(); } + @Override public void wakeUpIfDozing(long time, View where) { if (mDozing) { PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -5381,6 +4472,11 @@ public class StatusBar extends SystemUI implements DemoMode, } @Override + public boolean isDeviceLocked(int userId) { + return mKeyguardManager.isDeviceLocked(userId); + } + + @Override public void appTransitionCancelled() { EventBus.getDefault().send(new AppTransitionFinishedEvent()); } @@ -5418,12 +4514,12 @@ public class StatusBar extends SystemUI implements DemoMode, if (!mDeviceInteractive) { // Avoid flickering of the scrim when we instant launch the camera and the bouncer // comes on. - mScrimController.dontAnimateBouncerChangesUntilNextFrame(); mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L); } if (isScreenTurningOnOrOn()) { if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Launching camera"); mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source); + updateScrimController(); } else { // We need to defer the camera launch until the screen comes on, since otherwise // we will dismiss us too early since we are waiting on an activity to be drawn and @@ -5436,11 +4532,14 @@ public class StatusBar extends SystemUI implements DemoMode, } boolean isCameraAllowedByAdmin() { - if (mDevicePolicyManager.getCameraDisabled(null, mCurrentUserId)) { + if (mDevicePolicyManager.getCameraDisabled(null, + mLockscreenUserManager.getCurrentUserId())) { return false; - } else if (isKeyguardShowing() && isKeyguardSecure()) { + } else if (mStatusBarKeyguardViewManager == null || + (isKeyguardShowing() && isKeyguardSecure())) { // Check if the admin has disabled the camera specifically for the keyguard - return (mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUserId) + return (mDevicePolicyManager. + getKeyguardDisabledFeatures(null, mLockscreenUserManager.getCurrentUserId()) & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0; } @@ -5459,26 +4558,56 @@ public class StatusBar extends SystemUI implements DemoMode, public void notifyFpAuthModeChanged() { updateDozing(); + updateScrimController(); } private void updateDozing() { Trace.beginSection("StatusBar#updateDozing"); // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked. - mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD + boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD || mFingerprintUnlockController.getMode() == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING; // When in wake-and-unlock we may not have received a change to mState // but we still should not be dozing, manually set to false. if (mFingerprintUnlockController.getMode() == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) { - mDozing = false; + dozing = false; } + mDozing = dozing; mStatusBarWindowManager.setDozing(mDozing); mStatusBarKeyguardViewManager.setDozing(mDozing); if (mAmbientIndicationContainer instanceof DozeReceiver) { ((DozeReceiver) mAmbientIndicationContainer).setDozing(mDozing); } updateDozingState(); + updateReportRejectedTouchVisibility(); + Trace.endSection(); + } + + @VisibleForTesting + void updateScrimController() { + Trace.beginSection("StatusBar#updateScrimController"); + + // We don't want to end up in KEYGUARD state when we're unlocking with + // fingerprint from doze. We should cross fade directly from black. + final boolean wakeAndUnlocking = mFingerprintUnlockController.getMode() + == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK; + + if (mBouncerShowing) { + mScrimController.transitionTo(ScrimState.BOUNCER); + } else if (mLaunchCameraOnScreenTurningOn || isInLaunchTransition()) { + mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); + } else if (mBrightnessMirrorVisible) { + mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR); + } else if (isPulsing()) { + // Handled in DozeScrimController#setPulsing + } else if (mDozing) { + mScrimController.transitionTo(ScrimState.AOD); + } else if (mIsKeyguard && !wakeAndUnlocking) { + mScrimController.transitionTo(ScrimState.KEYGUARD); + } else { + mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); + } Trace.endSection(); } @@ -5491,8 +4620,9 @@ public class StatusBar extends SystemUI implements DemoMode, } private final class DozeServiceHost implements DozeHost { - private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); + private final ArrayList<Callback> mCallbacks = new ArrayList<>(); private boolean mAnimateWakeup; + private boolean mAnimateScreenOff; private boolean mIgnoreTouchWhilePulsing; @Override @@ -5541,29 +4671,25 @@ public class StatusBar extends SystemUI implements DemoMode, } mDozeScrimController.pulse(new PulseCallback() { - @Override public void onPulseStarted() { callback.onPulseStarted(); - Collection<HeadsUpManager.HeadsUpEntry> pulsingEntries = - mHeadsUpManager.getAllEntries(); - if (!pulsingEntries.isEmpty()) { + if (mHeadsUpManager.hasHeadsUpNotifications()) { // Only pulse the stack scroller if there's actually something to show. // Otherwise just show the always-on screen. - setPulsing(pulsingEntries); + setPulsing(true); } } @Override public void onPulseFinished() { callback.onPulseFinished(); - setPulsing(null); + setPulsing(false); } - private void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) { - mStackScroller.setPulsing(pulsing); - mNotificationPanel.setPulsing(pulsing != null); - mVisualStabilityManager.setPulsing(pulsing != null); + private void setPulsing(boolean pulsing) { + mNotificationPanel.setPulsing(pulsing); + mVisualStabilityManager.setPulsing(pulsing); mIgnoreTouchWhilePulsing = false; } }, reason); @@ -5591,7 +4717,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void dozeTimeTick() { - mNotificationPanel.refreshTime(); + mNotificationPanel.dozeTimeTick(); } @Override @@ -5626,11 +4752,6 @@ public class StatusBar extends SystemUI implements DemoMode, } @Override - public void abortPulsing() { - mDozeScrimController.abortPulsing(); - } - - @Override public void extendPulse() { mDozeScrimController.extendPulse(); } @@ -5646,8 +4767,13 @@ public class StatusBar extends SystemUI implements DemoMode, } @Override + public void setAnimateScreenOff(boolean animateScreenOff) { + mAnimateScreenOff = animateScreenOff; + } + + @Override public void onDoubleTap(float screenX, float screenY) { - if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null + if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) { mAmbientIndicationContainer.getLocationOnScreen(mTmpInt2); float viewX = screenX - mTmpInt2[0]; @@ -5666,7 +4792,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void setAodDimmingScrim(float scrimOpacity) { - mDozeScrimController.setAodDimmingScrim(scrimOpacity); + ScrimState.AOD.setAodFrontScrimAlpha(scrimOpacity); } public void dispatchDoubleTap(float viewX, float viewY) { @@ -5689,6 +4815,10 @@ public class StatusBar extends SystemUI implements DemoMode, private boolean shouldAnimateWakeup() { return mAnimateWakeup; } + + public boolean shouldAnimateScreenOff() { + return mAnimateScreenOff; + } } public boolean shouldIgnoreTouch() { @@ -5701,65 +4831,36 @@ public class StatusBar extends SystemUI implements DemoMode, protected IStatusBarService mBarService; // all notifications - protected NotificationData mNotificationData; protected NotificationStackScrollLayout mStackScroller; - protected NotificationGroupManager mGroupManager = new NotificationGroupManager(); + protected NotificationGroupManager mGroupManager; - protected RemoteInputController mRemoteInputController; // for heads up notifications - protected HeadsUpManager mHeadsUpManager; + protected HeadsUpManagerPhone mHeadsUpManager; private AboveShelfObserver mAboveShelfObserver; // handling reordering - protected VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager(); - - protected int mCurrentUserId = 0; - final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>(); + protected VisualStabilityManager mVisualStabilityManager; - protected int mLayoutDirection = -1; // invalid protected AccessibilityManager mAccessibilityManager; protected boolean mDeviceInteractive; protected boolean mVisible; - protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>(); - protected ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>(); - - /** - * Notifications with keys in this set are not actually around anymore. We kept them around - * when they were canceled in response to a remote input interaction. This allows us to show - * what you replied and allows you to continue typing into it. - */ - protected ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>(); // mScreenOnFromKeyguard && mVisible. private boolean mVisibleToUser; - private Locale mLocale; - - protected boolean mUseHeadsUp = false; - protected boolean mHeadsUpTicker = false; - protected boolean mDisableNotificationAlerts = false; - protected DevicePolicyManager mDevicePolicyManager; protected PowerManager mPowerManager; protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - // public mode, private notifications, etc - private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray(); - private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray(); - private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray(); - - private UserManager mUserManager; - protected KeyguardManager mKeyguardManager; private LockPatternUtils mLockPatternUtils; private DeviceProvisionedController mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); - protected SystemServicesProxy mSystemServicesProxy; // UI-specific methods @@ -5770,26 +4871,14 @@ public class StatusBar extends SystemUI implements DemoMode, protected RecentsComponent mRecents; - protected int mZenMode; - - // which notification is currently being longpress-examined by the user - private NotificationGuts mNotificationGutsExposed; - private MenuItem mGutsMenuItem; - - private KeyboardShortcuts mKeyboardShortcuts; - protected NotificationShelf mNotificationShelf; protected DismissView mDismissView; protected EmptyShadeView mEmptyShadeView; - private NotificationClicker mNotificationClicker = new NotificationClicker(); - protected AssistManager mAssistManager; protected boolean mVrMode; - private Set<String> mNonBlockablePkgs; - public boolean isDeviceInteractive() { return mDeviceInteractive; } @@ -5810,287 +4899,15 @@ public class StatusBar extends SystemUI implements DemoMode, return mVrMode; } - private final DeviceProvisionedListener mDeviceProvisionedListener = - new DeviceProvisionedListener() { - @Override - public void onDeviceProvisionedChanged() { - updateNotifications(); - } - }; - - protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - final int mode = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); - setZenMode(mode); - - updateLockscreenNotificationSetting(); - } - }; - - private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or - // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ... - mUsersAllowingPrivateNotifications.clear(); - mUsersAllowingNotifications.clear(); - // ... and refresh all the notifications - updateLockscreenNotificationSetting(); - updateNotifications(); - } - }; - - private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { - - @Override - public boolean onClickHandler( - final View view, final PendingIntent pendingIntent, final Intent fillInIntent) { - wakeUpIfDozing(SystemClock.uptimeMillis(), view); - - - if (handleRemoteInput(view, pendingIntent, fillInIntent)) { - return true; - } - - if (DEBUG) { - Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); - } - logActionClick(view); - // The intent we are sending is for the application, which - // won't have permission to immediately start an activity after - // the user switches to home. We know it is safe to do at this - // point, so make sure new activity switches are now allowed. - try { - ActivityManager.getService().resumeAppSwitches(); - } catch (RemoteException e) { - } - final boolean isActivity = pendingIntent.isActivity(); - if (isActivity) { - final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); - final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity( - mContext, pendingIntent.getIntent(), mCurrentUserId); - dismissKeyguardThenExecute(new OnDismissAction() { - @Override - public boolean onDismiss() { - try { - ActivityManager.getService().resumeAppSwitches(); - } catch (RemoteException e) { - } - - boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent); - - // close the shade if it was open - if (handled && !mNotificationPanel.isFullyCollapsed()) { - animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, - true /* force */); - visibilityChanged(false); - mAssistManager.hideAssist(); - - // Wait for activity start. - return true; - } else { - return false; - } - - } - }, afterKeyguardGone); - return true; - } else { - return superOnClickHandler(view, pendingIntent, fillInIntent); - } - } - - private void logActionClick(View view) { - ViewParent parent = view.getParent(); - String key = getNotificationKeyForParent(parent); - if (key == null) { - Log.w(TAG, "Couldn't determine notification for click."); - return; - } - int index = -1; - // If this is a default template, determine the index of the button. - if (view.getId() == com.android.internal.R.id.action0 && - parent != null && parent instanceof ViewGroup) { - ViewGroup actionGroup = (ViewGroup) parent; - index = actionGroup.indexOfChild(view); - } - try { - mBarService.onNotificationActionClick(key, index); - } catch (RemoteException e) { - // Ignore - } - } - - private String getNotificationKeyForParent(ViewParent parent) { - while (parent != null) { - if (parent instanceof ExpandableNotificationRow) { - return ((ExpandableNotificationRow) parent).getStatusBarNotification().getKey(); - } - parent = parent.getParent(); - } - return null; - } - - private boolean superOnClickHandler(View view, PendingIntent pendingIntent, - Intent fillInIntent) { - return super.onClickHandler(view, pendingIntent, fillInIntent, - StackId.FULLSCREEN_WORKSPACE_STACK_ID); - } - - private boolean handleRemoteInput(View view, PendingIntent pendingIntent, Intent fillInIntent) { - Object tag = view.getTag(com.android.internal.R.id.remote_input_tag); - RemoteInput[] inputs = null; - if (tag instanceof RemoteInput[]) { - inputs = (RemoteInput[]) tag; - } - - if (inputs == null) { - return false; - } - - RemoteInput input = null; - - for (RemoteInput i : inputs) { - if (i.getAllowFreeFormInput()) { - input = i; - } - } - - if (input == null) { - return false; - } - - ViewParent p = view.getParent(); - RemoteInputView riv = null; - while (p != null) { - if (p instanceof View) { - View pv = (View) p; - if (pv.isRootNamespace()) { - riv = findRemoteInputView(pv); - break; - } - } - p = p.getParent(); - } - ExpandableNotificationRow row = null; - while (p != null) { - if (p instanceof ExpandableNotificationRow) { - row = (ExpandableNotificationRow) p; - break; - } - p = p.getParent(); - } - - if (row == null) { - return false; - } - - row.setUserExpanded(true); - - if (!mAllowLockscreenRemoteInput) { - final int userId = pendingIntent.getCreatorUserHandle().getIdentifier(); - if (isLockscreenPublicMode(userId)) { - onLockedRemoteInput(row, view); - return true; - } - if (mUserManager.getUserInfo(userId).isManagedProfile() - && mKeyguardManager.isDeviceLocked(userId)) { - onLockedWorkRemoteInput(userId, row, view); - return true; - } - } - - if (riv == null) { - riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild()); - if (riv == null) { - return false; - } - if (!row.getPrivateLayout().getExpandedChild().isShown()) { - onMakeExpandedVisibleForRemoteInput(row, view); - return true; - } - } - - int width = view.getWidth(); - if (view instanceof TextView) { - // Center the reveal on the text which might be off-center from the TextView - TextView tv = (TextView) view; - if (tv.getLayout() != null) { - int innerWidth = (int) tv.getLayout().getLineWidth(0); - innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight(); - width = Math.min(width, innerWidth); - } - } - int cx = view.getLeft() + width / 2; - int cy = view.getTop() + view.getHeight() / 2; - int w = riv.getWidth(); - int h = riv.getHeight(); - int r = Math.max( - Math.max(cx + cy, cx + (h - cy)), - Math.max((w - cx) + cy, (w - cx) + (h - cy))); - - riv.setRevealParameters(cx, cy, r); - riv.setPendingIntent(pendingIntent); - riv.setRemoteInput(inputs, input); - riv.focusAnimated(); - - return true; - } - - private RemoteInputView findRemoteInputView(View v) { - if (v == null) { - return null; - } - return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG); - } - }; - - private final BroadcastReceiver mBaseBroadcastReceiver = new BroadcastReceiver() { + private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (Intent.ACTION_USER_SWITCHED.equals(action)) { - mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - updateCurrentProfilesCache(); - if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); - - updateLockscreenNotificationSetting(); - - userSwitched(mCurrentUserId); - } else if (Intent.ACTION_USER_ADDED.equals(action)) { - updateCurrentProfilesCache(); - } else if (Intent.ACTION_USER_PRESENT.equals(action)) { - List<ActivityManager.RecentTaskInfo> recentTask = null; - try { - recentTask = ActivityManager.getService().getRecentTasks(1, - ActivityManager.RECENT_WITH_EXCLUDED - | ActivityManager.RECENT_INCLUDE_PROFILES, - mCurrentUserId).getList(); - } catch (RemoteException e) { - // Abandon hope activity manager not running. - } - if (recentTask != null && recentTask.size() > 0) { - UserInfo user = mUserManager.getUserInfo(recentTask.get(0).userId); - if (user != null && user.isManagedProfile()) { - Toast toast = Toast.makeText(mContext, - R.string.managed_profile_foreground_toast, - Toast.LENGTH_SHORT); - TextView text = (TextView) toast.getView().findViewById( - android.R.id.message); - text.setCompoundDrawablesRelativeWithIntrinsicBounds( - R.drawable.stat_sys_managed_profile_status, 0, 0, 0); - int paddingPx = mContext.getResources().getDimensionPixelSize( - R.dimen.managed_profile_toast_padding); - text.setCompoundDrawablePadding(paddingPx); - toast.show(); - } - } - } else if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) { + if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) { NotificationManager noMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - noMan.cancel(SystemMessage.NOTE_HIDDEN_NOTIFICATIONS); + noMan.cancel(com.android.internal.messages.nano.SystemMessageProto.SystemMessage. + NOTE_HIDDEN_NOTIFICATIONS); Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0); @@ -6102,148 +4919,181 @@ public class StatusBar extends SystemUI implements DemoMode, ); } - } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) { - final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT); - final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX); - if (intentSender != null) { - try { - mContext.startIntentSender(intentSender, null, 0, 0, 0); - } catch (IntentSender.SendIntentException e) { - /* ignore */ - } - } - if (notificationKey != null) { - try { - mBarService.onNotificationClick(notificationKey); - } catch (RemoteException e) { - /* ignore */ - } - } } } }; - private final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - - if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) && - isCurrentProfile(getSendingUserId())) { - mUsersAllowingPrivateNotifications.clear(); - updateLockscreenNotificationSetting(); - updateNotifications(); - } else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) { - if (userId != mCurrentUserId && isCurrentProfile(userId)) { - onWorkChallengeChanged(); - } - } - } - }; + @Override + public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) { + Notification notification = sbn.getNotification(); + final PendingIntent intent = notification.contentIntent != null + ? notification.contentIntent + : notification.fullScreenIntent; + final String notificationKey = sbn.getKey(); - private final NotificationListenerWithPlugins mNotificationListener = - new NotificationListenerWithPlugins() { - @Override - public void onListenerConnected() { - if (DEBUG) Log.d(TAG, "onListenerConnected"); - onPluginConnected(); - final StatusBarNotification[] notifications = getActiveNotifications(); - if (notifications == null) { - Log.w(TAG, "onListenerConnected unable to get active notifications."); - return; + final boolean afterKeyguardGone = intent.isActivity() + && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(), + mLockscreenUserManager.getCurrentUserId()); + dismissKeyguardThenExecute(() -> { + // TODO: Some of this code may be able to move to NotificationEntryManager. + if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) { + // Release the HUN notification to the shade. + + if (isPresenterFullyCollapsed()) { + HeadsUpUtil.setIsClickedHeadsUpNotification(row, true); + } + // + // In most cases, when FLAG_AUTO_CANCEL is set, the notification will + // become canceled shortly by NoMan, but we can't assume that. + mHeadsUpManager.releaseImmediately(notificationKey); + } + StatusBarNotification parentToCancel = null; + if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) { + StatusBarNotification summarySbn = + mGroupManager.getLogicalGroupSummary(sbn).getStatusBarNotification(); + if (shouldAutoCancel(summarySbn)) { + parentToCancel = summarySbn; + } } - final RankingMap currentRanking = getCurrentRanking(); - mHandler.post(new Runnable() { - @Override - public void run() { - for (StatusBarNotification sbn : notifications) { - try { - addNotification(sbn, currentRanking); - } catch (InflationException e) { - handleInflationException(sbn, e); + final StatusBarNotification parentToCancelFinal = parentToCancel; + final Runnable runnable = () -> { + try { + // The intent we are sending is for the application, which + // won't have permission to immediately start an activity after + // the user switches to home. We know it is safe to do at this + // point, so make sure new activity switches are now allowed. + ActivityManager.getService().resumeAppSwitches(); + } catch (RemoteException e) { + } + int launchResult = ActivityManager.START_CANCELED; + if (intent != null) { + // If we are launching a work activity and require to launch + // separate work challenge, we defer the activity action and cancel + // notification until work challenge is unlocked. + if (intent.isActivity()) { + final int userId = intent.getCreatorUserHandle().getIdentifier(); + if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId) + && mKeyguardManager.isDeviceLocked(userId)) { + // TODO(b/28935539): should allow certain activities to + // bypass work challenge + if (startWorkChallengeIfNecessary(userId, intent.getIntentSender(), + notificationKey)) { + // Show work challenge, do not run PendingIntent and + // remove notification + collapsePanel(); + return; + } } } + Intent fillInIntent = null; + Entry entry = row.getEntry(); + CharSequence remoteInputText = null; + RemoteInputController controller = mRemoteInputManager.getController(); + if (controller.isRemoteInputActive(entry)) { + remoteInputText = row.getActiveRemoteInputText(); + } + if (TextUtils.isEmpty(remoteInputText) + && !TextUtils.isEmpty(entry.remoteInputText)) { + remoteInputText = entry.remoteInputText; + } + if (!TextUtils.isEmpty(remoteInputText) + && !controller.isSpinning(entry.key)) { + fillInIntent = new Intent().putExtra(Notification.EXTRA_REMOTE_INPUT_DRAFT, + remoteInputText.toString()); + } + try { + launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null, + null, null, getActivityOptions(row)); + mActivityLaunchAnimator.setLaunchResult(launchResult); + } catch (PendingIntent.CanceledException e) { + // the stack trace isn't very helpful here. + // Just log the exception message. + Log.w(TAG, "Sending contentIntent failed: " + e); + + // TODO: Dismiss Keyguard. + } + if (intent.isActivity()) { + mAssistManager.hideAssist(); + } + } + if (shouldCollapse()) { + if (Looper.getMainLooper().isCurrentThread()) { + collapsePanel(); + } else { + mStackScroller.post(this::collapsePanel); + } } - }); - } - @Override - public void onNotificationPosted(final StatusBarNotification sbn, - final RankingMap rankingMap) { - if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn); - if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) { - mHandler.post(new Runnable() { - @Override - public void run() { - processForRemoteInput(sbn.getNotification()); - String key = sbn.getKey(); - mKeysKeptForRemoteInput.remove(key); - boolean isUpdate = mNotificationData.get(key) != null; - // In case we don't allow child notifications, we ignore children of - // notifications that have a summary, since we're not going to show them - // anyway. This is true also when the summary is canceled, - // because children are automatically canceled by NoMan in that case. - if (!ENABLE_CHILD_NOTIFICATIONS - && mGroupManager.isChildInGroupWithSummary(sbn)) { - if (DEBUG) { - Log.d(TAG, "Ignoring group child due to existing summary: " + sbn); - } + try { + mBarService.onNotificationClick(notificationKey); + } catch (RemoteException ex) { + // system process is dead if we're here. + } + if (parentToCancelFinal != null) { + removeNotification(parentToCancelFinal); + } + if (shouldAutoCancel(sbn)) { + // Automatically remove all notifications that we may have kept around longer + removeNotification(sbn); + } + }; - // Remove existing notification to avoid stale data. - if (isUpdate) { - removeNotification(key, rankingMap); - } else { - mNotificationData.updateRanking(rankingMap); - } - return; - } - try { - if (isUpdate) { - updateNotification(sbn, rankingMap); - } else { - addNotification(sbn, rankingMap); - } - } catch (InflationException e) { - handleInflationException(sbn, e); - } - } - }); + if (mStatusBarKeyguardViewManager.isShowing() + && mStatusBarKeyguardViewManager.isOccluded()) { + mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable); + } else { + new Thread(runnable).start(); } - } - @Override - public void onNotificationRemoved(StatusBarNotification sbn, - final RankingMap rankingMap) { - if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn); - if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) { - final String key = sbn.getKey(); - mHandler.post(() -> removeNotification(key, rankingMap)); - } - } + return !mNotificationPanel.isFullyCollapsed(); + }, afterKeyguardGone); + } - @Override - public void onNotificationRankingUpdate(final RankingMap rankingMap) { - if (DEBUG) Log.d(TAG, "onRankingUpdate"); - if (rankingMap != null) { - RankingMap r = onPluginRankingUpdate(rankingMap); - mHandler.post(() -> updateNotificationRanking(r)); - } + private boolean shouldCollapse() { + return mState != StatusBarState.SHADE || !mActivityLaunchAnimator.isAnimationPending(); + } + + public void collapsePanel(boolean animate) { + if (animate) { + collapsePanel(); + } else if (!isPresenterFullyCollapsed()) { + instantCollapseNotificationPanel(); + visibilityChanged(false); + } else { + runPostCollapseRunnables(); } + } - }; + private boolean collapsePanel() { + if (!mNotificationPanel.isFullyCollapsed()) { + // close the shade if it was open + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */, + true /* delayed */); + visibilityChanged(false); - private void updateCurrentProfilesCache() { - synchronized (mCurrentProfiles) { - mCurrentProfiles.clear(); - if (mUserManager != null) { - for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) { - mCurrentProfiles.put(user.id, user); - } - } + return true; + } else { + return false; } } + private void removeNotification(StatusBarNotification notification) { + // We have to post it to the UI thread for synchronization + mHandler.post(() -> { + Runnable removeRunnable = + () -> mEntryManager.performRemoveNotification(notification); + if (isCollapsing()) { + // To avoid lags we're only performing the remove + // after the shade was collapsed + addPostCollapseAction(removeRunnable); + } else { + removeRunnable.run(); + } + }); + } + + protected NotificationListener mNotificationListener; + protected void notifyUserAboutHiddenNotifications() { if (0 != Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 1)) { @@ -6254,7 +5104,7 @@ public class StatusBar extends SystemUI implements DemoMode, Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0); return; } - Log.d(TAG, "disabling lockecreen notifications and alerting the user"); + Log.d(TAG, "disabling lockscreen notifications and alerting the user"); // disable lockscreen notifications until user acts on the banner. Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0); @@ -6295,31 +5145,12 @@ public class StatusBar extends SystemUI implements DemoMode, @Override // NotificationData.Environment public boolean isNotificationForCurrentProfiles(StatusBarNotification n) { - final int thisUserId = mCurrentUserId; final int notificationUserId = n.getUserId(); if (DEBUG && MULTIUSER_DEBUG) { - Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", - n, thisUserId, notificationUserId)); - } - return isCurrentProfile(notificationUserId); - } - - protected void setNotificationShown(StatusBarNotification n) { - setNotificationsShown(new String[]{n.getKey()}); - } - - protected void setNotificationsShown(String[] keys) { - try { - mNotificationListener.setNotificationsShown(keys); - } catch (RuntimeException e) { - Log.d(TAG, "failed setNotificationsShown: ", e); - } - } - - protected boolean isCurrentProfile(int userId) { - synchronized (mCurrentProfiles) { - return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null; + Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", n, + mLockscreenUserManager.getCurrentUserId(), notificationUserId)); } + return mLockscreenUserManager.isCurrentProfile(notificationUserId); } @Override @@ -6327,41 +5158,23 @@ public class StatusBar extends SystemUI implements DemoMode, return mGroupManager; } - public boolean isMediaNotification(NotificationData.Entry entry) { - // TODO: confirm that there's a valid media key - return entry.getExpandedContentView() != null && - entry.getExpandedContentView() - .findViewById(com.android.internal.R.id.media_actions) != null; - } - - // The button in the guts that links to the system notification settings for that app - private void startAppNotificationSettingsActivity(String packageName, final int appUid, - final NotificationChannel channel) { - final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); - intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName); - intent.putExtra(Settings.EXTRA_APP_UID, appUid); - if (channel != null) { - intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId()); - } - startNotificationGutsIntent(intent, appUid); - } - - private void startNotificationGutsIntent(final Intent intent, final int appUid) { - dismissKeyguardThenExecute(new OnDismissAction() { - @Override - public boolean onDismiss() { - AsyncTask.execute(new Runnable() { - @Override - public void run() { - TaskStackBuilder.create(mContext) - .addNextIntentWithParentStack(intent) - .startActivities(getActivityOptions(), - new UserHandle(UserHandle.getUserId(appUid))); - } - }); - animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */); - return true; - } + @Override + public void startNotificationGutsIntent(final Intent intent, final int appUid, + ExpandableNotificationRow row) { + dismissKeyguardThenExecute(() -> { + AsyncTask.execute(() -> { + int launchResult = TaskStackBuilder.create(mContext) + .addNextIntentWithParentStack(intent) + .startActivities(getActivityOptions(row), + new UserHandle(UserHandle.getUserId(appUid))); + mActivityLaunchAnimator.setLaunchResult(launchResult); + if (shouldCollapse()) { + // Putting it back on the main thread, since we're touching views + mStatusBarWindow.post(() -> animateCollapsePanels( + CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */)); + } + }); + return true; }, false /* afterKeyguardGone */); } @@ -6375,237 +5188,15 @@ public class StatusBar extends SystemUI implements DemoMode, } } - private void bindGuts(final ExpandableNotificationRow row, MenuItem item) { - row.inflateGuts(); - row.setGutsView(item); - final StatusBarNotification sbn = row.getStatusBarNotification(); - row.setTag(sbn.getPackageName()); - final NotificationGuts guts = row.getGuts(); - guts.setClosedListener((NotificationGuts g) -> { - if (!g.willBeRemoved() && !row.isRemoved()) { - mStackScroller.onHeightChanged(row, !isPanelFullyCollapsed() /* needsAnimation */); - } - if (mNotificationGutsExposed == g) { - mNotificationGutsExposed = null; - mGutsMenuItem = null; - } - String key = sbn.getKey(); - if (key.equals(mKeyToRemoveOnGutsClosed)) { - mKeyToRemoveOnGutsClosed = null; - removeNotification(key, mLatestRankingMap); - } - }); - - View gutsView = item.getGutsView(); - if (gutsView instanceof NotificationSnooze) { - NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView; - snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper()); - snoozeGuts.setStatusBarNotification(sbn); - snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria); - guts.setHeightChangedListener((NotificationGuts g) -> { - mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */); - }); - } - - if (gutsView instanceof NotificationInfo) { - final UserHandle userHandle = sbn.getUser(); - PackageManager pmUser = getPackageManagerForUser(mContext, - userHandle.getIdentifier()); - final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface( - ServiceManager.getService(Context.NOTIFICATION_SERVICE)); - final String pkg = sbn.getPackageName(); - NotificationInfo info = (NotificationInfo) gutsView; - // Settings link is only valid for notifications that specify a user, unless this is the - // system user. - NotificationInfo.OnSettingsClickListener onSettingsClick = null; - if (!userHandle.equals(UserHandle.ALL) || mCurrentUserId == UserHandle.USER_SYSTEM) { - onSettingsClick = (View v, NotificationChannel channel, int appUid) -> { - mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO); - guts.resetFalsingCheck(); - startAppNotificationSettingsActivity(pkg, appUid, channel); - }; - } - final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v, - Intent intent) -> { - mMetricsLogger.action(MetricsEvent.ACTION_APP_NOTE_SETTINGS); - guts.resetFalsingCheck(); - startNotificationGutsIntent(intent, sbn.getUid()); - }; - final View.OnClickListener onDoneClick = (View v) -> { - saveAndCloseNotificationMenu(info, row, guts, v); - }; - final NotificationInfo.CheckSaveListener checkSaveListener = - (Runnable saveImportance) -> { - // If the user has security enabled, show challenge if the setting is changed. - if (isLockscreenPublicMode(userHandle.getIdentifier()) - && (mState == StatusBarState.KEYGUARD - || mState == StatusBarState.SHADE_LOCKED)) { - onLockedNotificationImportanceChange(() -> { - saveImportance.run(); - return true; - }); - } else { - saveImportance.run(); - } - }; - - ArraySet<NotificationChannel> channels = new ArraySet<NotificationChannel>(); - channels.add(row.getEntry().channel); - if (row.isSummaryWithChildren()) { - // If this is a summary, then add in the children notification channels for the - // same user and pkg. - final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren(); - final int numChildren = childrenRows.size(); - for (int i = 0; i < numChildren; i++) { - final ExpandableNotificationRow childRow = childrenRows.get(i); - final NotificationChannel childChannel = childRow.getEntry().channel; - final StatusBarNotification childSbn = childRow.getStatusBarNotification(); - if (childSbn.getUser().equals(userHandle) && - childSbn.getPackageName().equals(pkg)) { - channels.add(childChannel); - } - } - } - try { - info.bindNotification(pmUser, iNotificationManager, pkg, new ArrayList(channels), - row.getEntry().channel.getImportance(), sbn, onSettingsClick, - onAppSettingsClick, onDoneClick, checkSaveListener, - mNonBlockablePkgs); - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - } - } - } - - private void saveAndCloseNotificationMenu(NotificationInfo info, - ExpandableNotificationRow row, NotificationGuts guts, View done) { - guts.resetFalsingCheck(); - int[] rowLocation = new int[2]; - int[] doneLocation = new int[2]; - row.getLocationOnScreen(rowLocation); - done.getLocationOnScreen(doneLocation); - - final int centerX = done.getWidth() / 2; - final int centerY = done.getHeight() / 2; - final int x = doneLocation[0] - rowLocation[0] + centerX; - final int y = doneLocation[1] - rowLocation[1] + centerY; - closeAndSaveGuts(false /* removeLeavebehind */, false /* force */, - true /* removeControls */, x, y, true /* resetMenu */); - } - - protected SwipeHelper.LongPressListener getNotificationLongClicker() { - return new SwipeHelper.LongPressListener() { - @Override - public boolean onLongPress(View v, final int x, final int y, - MenuItem item) { - if (!(v instanceof ExpandableNotificationRow)) { - return false; - } - if (v.getWindowToken() == null) { - Log.e(TAG, "Trying to show notification guts, but not attached to window"); - return false; - } - - final ExpandableNotificationRow row = (ExpandableNotificationRow) v; - if (row.isDark()) { - return false; - } - v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - if (row.areGutsExposed()) { - closeAndSaveGuts(false /* removeLeavebehind */, false /* force */, - true /* removeControls */, -1 /* x */, -1 /* y */, - true /* resetMenu */); - return false; - } - bindGuts(row, item); - NotificationGuts guts = row.getGuts(); - - // Assume we are a status_bar_notification_row - if (guts == null) { - // This view has no guts. Examples are the more card or the dismiss all view - return false; - } - - mMetricsLogger.action(MetricsEvent.ACTION_NOTE_CONTROLS); - - // ensure that it's laid but not visible until actually laid out - guts.setVisibility(View.INVISIBLE); - // Post to ensure the the guts are properly laid out. - guts.post(new Runnable() { - @Override - public void run() { - if (row.getWindowToken() == null) { - Log.e(TAG, "Trying to show notification guts, but not attached to " - + "window"); - return; - } - closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, - true /* removeControls */, -1 /* x */, -1 /* y */, - false /* resetMenu */); - guts.setVisibility(View.VISIBLE); - final double horz = Math.max(guts.getWidth() - x, x); - final double vert = Math.max(guts.getHeight() - y, y); - final float r = (float) Math.hypot(horz, vert); - final Animator a - = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r); - a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); - a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - a.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - // Move the notification view back over the menu - row.resetTranslation(); - } - }); - a.start(); - final boolean needsFalsingProtection = - (mState == StatusBarState.KEYGUARD && - !mAccessibilityManager.isTouchExplorationEnabled()); - guts.setExposed(true /* exposed */, needsFalsingProtection); - row.closeRemoteInput(); - mStackScroller.onHeightChanged(row, true /* needsAnimation */); - mNotificationGutsExposed = guts; - mGutsMenuItem = item; - } - }); - return true; - } - }; - } - - /** - * Returns the exposed NotificationGuts or null if none are exposed. - */ - public NotificationGuts getExposedGuts() { - return mNotificationGutsExposed; - } - - /** - * Closes guts or notification menus that might be visible and saves any changes. - * - * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed. - * @param force true if guts should be closed regardless of state (used for snooze only). - * @param removeControls true if controls (e.g. info) should be closed. - * @param x if closed based on touch location, this is the x touch location. - * @param y if closed based on touch location, this is the y touch location. - * @param resetMenu if any notification menus that might be revealed should be closed. - */ - public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls, - int x, int y, boolean resetMenu) { - if (mNotificationGutsExposed != null) { - mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force); - } - if (resetMenu) { - mStackScroller.resetExposedMenuView(false /* animate */, true /* force */); - } - } - @Override public void toggleSplitScreen() { toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */); } + void awakenDreams() { + SystemServicesProxy.getInstance(mContext).awakenDreamsAsync(); + } + @Override public void preloadRecentApps() { int msg = MSG_PRELOAD_RECENT_APPS; @@ -6646,13 +5237,6 @@ public class StatusBar extends SystemUI implements DemoMode, updateHideIconsForBouncer(true /* animate */); } - protected void sendCloseSystemWindows(String reason) { - try { - ActivityManager.getService().closeSystemDialogs(reason); - } catch (RemoteException e) { - } - } - protected void toggleKeyboardShortcuts(int deviceId) { KeyboardShortcuts.toggle(mContext, deviceId); } @@ -6661,89 +5245,14 @@ public class StatusBar extends SystemUI implements DemoMode, KeyboardShortcuts.dismiss(); } - /** - * Save the current "public" (locked and secure) state of the lockscreen. - */ - public void setLockscreenPublicMode(boolean publicMode, int userId) { - mLockscreenPublicMode.put(userId, publicMode); - } - - public boolean isLockscreenPublicMode(int userId) { - if (userId == UserHandle.USER_ALL) { - return mLockscreenPublicMode.get(mCurrentUserId, false); - } - return mLockscreenPublicMode.get(userId, false); - } - - /** - * Has the given user chosen to allow notifications to be shown even when the lockscreen is in - * "public" (secure & locked) mode? - */ - public boolean userAllowsNotificationsInPublic(int userHandle) { - if (userHandle == UserHandle.USER_ALL) { - return true; - } - - if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) { - final boolean allowed = 0 != Settings.Secure.getIntForUser( - mContext.getContentResolver(), - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle); - mUsersAllowingNotifications.append(userHandle, allowed); - return allowed; - } - - return mUsersAllowingNotifications.get(userHandle); - } - - /** - * Has the given user chosen to allow their private (full) notifications to be shown even - * when the lockscreen is in "public" (secure & locked) mode? - */ - public boolean userAllowsPrivateNotificationsInPublic(int userHandle) { - if (userHandle == UserHandle.USER_ALL) { - return true; - } - - if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { - final boolean allowedByUser = 0 != Settings.Secure.getIntForUser( - mContext.getContentResolver(), - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); - final boolean allowedByDpm = adminAllowsUnredactedNotifications(userHandle); - final boolean allowed = allowedByUser && allowedByDpm; - mUsersAllowingPrivateNotifications.append(userHandle, allowed); - return allowed; - } - - return mUsersAllowingPrivateNotifications.get(userHandle); - } - - private boolean adminAllowsUnredactedNotifications(int userHandle) { - if (userHandle == UserHandle.USER_ALL) { - return true; - } - final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */, - userHandle); - return (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0; - } - - /** - * Returns true if we're on a secure lockscreen and the user wants to hide notification data. - * If so, notifications should be hidden. - */ @Override // NotificationData.Environment public boolean shouldHideNotifications(int userId) { - return isLockscreenPublicMode(userId) && !userAllowsNotificationsInPublic(userId) - || (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId)); + return mLockscreenUserManager.shouldHideNotifications(userId); } - /** - * Returns true if we're on a secure lockscreen and the user wants to hide notifications via - * package-specific override. - */ @Override // NotificationDate.Environment public boolean shouldHideNotifications(String key) { - return isLockscreenPublicMode(mCurrentUserId) - && mNotificationData.getVisibilityOverride(key) == Notification.VISIBILITY_SECRET; + return mLockscreenUserManager.shouldHideNotifications(key); } /** @@ -6751,19 +5260,7 @@ public class StatusBar extends SystemUI implements DemoMode, */ @Override // NotificationData.Environment public boolean isSecurelyLocked(int userId) { - return isLockscreenPublicMode(userId); - } - - public void onNotificationClear(StatusBarNotification notification) { - try { - mBarService.onNotificationClear( - notification.getPackageName(), - notification.getTag(), - notification.getId(), - notification.getUserId()); - } catch (android.os.RemoteException ex) { - // oh well - } + return mLockscreenUserManager.isLockscreenPublicMode(userId); } /** @@ -6777,145 +5274,10 @@ public class StatusBar extends SystemUI implements DemoMode, if (mState == StatusBarState.KEYGUARD) { // Since the number of notifications is determined based on the height of the view, we // need to update them. - int maxBefore = getMaxKeyguardNotifications(false /* recompute */); - int maxNotifications = getMaxKeyguardNotifications(true /* recompute */); + int maxBefore = getMaxNotificationsWhileLocked(false /* recompute */); + int maxNotifications = getMaxNotificationsWhileLocked(true /* recompute */); if (maxBefore != maxNotifications) { - updateRowStates(); - } - } - } - - protected void inflateViews(Entry entry, ViewGroup parent) { - PackageManager pmUser = getPackageManagerForUser(mContext, - entry.notification.getUser().getIdentifier()); - - final StatusBarNotification sbn = entry.notification; - if (entry.row != null) { - entry.reset(); - updateNotification(entry, pmUser, sbn, entry.row); - } else { - new RowInflaterTask().inflate(mContext, parent, entry, - row -> { - bindRow(entry, pmUser, sbn, row); - updateNotification(entry, pmUser, sbn, row); - }); - } - - } - - private void bindRow(Entry entry, PackageManager pmUser, - StatusBarNotification sbn, ExpandableNotificationRow row) { - row.setExpansionLogger(this, entry.notification.getKey()); - row.setGroupManager(mGroupManager); - row.setHeadsUpManager(mHeadsUpManager); - row.setAboveShelfChangedListener(mAboveShelfObserver); - row.setRemoteInputController(mRemoteInputController); - row.setOnExpandClickListener(this); - row.setRemoteViewClickHandler(mOnClickHandler); - row.setInflationCallback(this); - row.setSecureStateProvider(this::isKeyguardCurrentlySecure); - - // Get the app name. - // Note that Notification.Builder#bindHeaderAppName has similar logic - // but since this field is used in the guts, it must be accurate. - // Therefore we will only show the application label, or, failing that, the - // package name. No substitutions. - final String pkg = sbn.getPackageName(); - String appname = pkg; - try { - final ApplicationInfo info = pmUser.getApplicationInfo(pkg, - PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_DISABLED_COMPONENTS); - if (info != null) { - appname = String.valueOf(pmUser.getApplicationLabel(info)); - } - } catch (NameNotFoundException e) { - // Do nothing - } - row.setAppName(appname); - row.setOnDismissRunnable(() -> - performRemoveNotification(row.getStatusBarNotification())); - row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - if (ENABLE_REMOTE_INPUT) { - row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); - } - } - - private void updateNotification(Entry entry, PackageManager pmUser, - StatusBarNotification sbn, ExpandableNotificationRow row) { - row.setNeedsRedaction(needsRedaction(entry)); - boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey()); - boolean isUpdate = mNotificationData.get(entry.key) != null; - boolean wasLowPriority = row.isLowPriority(); - row.setIsLowPriority(isLowPriority); - row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority)); - // bind the click event to the content area - mNotificationClicker.register(row, sbn); - - // Extract target SDK version. - try { - ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0); - entry.targetSdk = info.targetSdkVersion; - } catch (NameNotFoundException ex) { - Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); - } - row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD - && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); - entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); - entry.autoRedacted = entry.notification.getNotification().publicVersion == null; - - entry.row = row; - entry.row.setOnActivatedListener(this); - - boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn, - mNotificationData.getImportance(sbn.getKey())); - boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && mPanelExpanded; - row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); - row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); - row.updateNotification(entry); - } - - /** - * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this - * via first-class API. - * - * TODO: Remove once enough apps specify remote inputs on their own. - */ - private void processForRemoteInput(Notification n) { - if (!ENABLE_REMOTE_INPUT) return; - - if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") && - (n.actions == null || n.actions.length == 0)) { - Notification.Action viableAction = null; - Notification.WearableExtender we = new Notification.WearableExtender(n); - - List<Notification.Action> actions = we.getActions(); - final int numActions = actions.size(); - - for (int i = 0; i < numActions; i++) { - Notification.Action action = actions.get(i); - if (action == null) { - continue; - } - RemoteInput[] remoteInputs = action.getRemoteInputs(); - if (remoteInputs == null) { - continue; - } - for (RemoteInput ri : remoteInputs) { - if (ri.getAllowFreeFormInput()) { - viableAction = action; - break; - } - } - if (viableAction != null) { - break; - } - } - - if (viableAction != null) { - Notification.Builder rebuilder = Notification.Builder.recoverBuilder(mContext, n); - rebuilder.setActions(viableAction); - rebuilder.build(); // will rewrite n + mViewHierarchyManager.updateRowStates(); } } } @@ -6923,240 +5285,59 @@ public class StatusBar extends SystemUI implements DemoMode, public void startPendingIntentDismissingKeyguard(final PendingIntent intent) { if (!isDeviceProvisioned()) return; - final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); final boolean afterKeyguardGone = intent.isActivity() && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(), - mCurrentUserId); - dismissKeyguardThenExecute(new OnDismissAction() { - @Override - public boolean onDismiss() { - new Thread() { - @Override - public void run() { - try { - // The intent we are sending is for the application, which - // won't have permission to immediately start an activity after - // the user switches to home. We know it is safe to do at this - // point, so make sure new activity switches are now allowed. - ActivityManager.getService().resumeAppSwitches(); - } catch (RemoteException e) { - } - try { - intent.send(null, 0, null, null, null, null, getActivityOptions()); - } catch (PendingIntent.CanceledException e) { - // the stack trace isn't very helpful here. - // Just log the exception message. - Log.w(TAG, "Sending intent failed: " + e); - - // TODO: Dismiss Keyguard. - } - if (intent.isActivity()) { - mAssistManager.hideAssist(); - } - } - }.start(); - - if (!mNotificationPanel.isFullyCollapsed()) { - // close the shade if it was open - animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, - true /* force */, true /* delayed */); - visibilityChanged(false); - - return true; - } else { - return false; + mLockscreenUserManager.getCurrentUserId()); + dismissKeyguardThenExecute(() -> { + new Thread(() -> { + try { + // The intent we are sending is for the application, which + // won't have permission to immediately start an activity after + // the user switches to home. We know it is safe to do at this + // point, so make sure new activity switches are now allowed. + ActivityManager.getService().resumeAppSwitches(); + } catch (RemoteException e) { } - } - }, afterKeyguardGone); - } - - - private final class NotificationClicker implements View.OnClickListener { - - @Override - public void onClick(final View v) { - if (!(v instanceof ExpandableNotificationRow)) { - Log.e(TAG, "NotificationClicker called on a view that is not a notification row."); - return; - } - - wakeUpIfDozing(SystemClock.uptimeMillis(), v); - - final ExpandableNotificationRow row = (ExpandableNotificationRow) v; - final StatusBarNotification sbn = row.getStatusBarNotification(); - if (sbn == null) { - Log.e(TAG, "NotificationClicker called on an unclickable notification,"); - return; - } - - // Check if the notification is displaying the menu, if so slide notification back - if (row.getProvider() != null && row.getProvider().isMenuVisible()) { - row.animateTranslateNotification(0); - return; - } - - Notification notification = sbn.getNotification(); - final PendingIntent intent = notification.contentIntent != null - ? notification.contentIntent - : notification.fullScreenIntent; - final String notificationKey = sbn.getKey(); + try { + intent.send(null, 0, null, null, null, null, getActivityOptions( + null /* sourceNotification */)); + } catch (PendingIntent.CanceledException e) { + // the stack trace isn't very helpful here. + // Just log the exception message. + Log.w(TAG, "Sending intent failed: " + e); - // Mark notification for one frame. - row.setJustClicked(true); - DejankUtils.postAfterTraversal(new Runnable() { - @Override - public void run() { - row.setJustClicked(false); + // TODO: Dismiss Keyguard. } - }); - - final boolean afterKeyguardGone = intent.isActivity() - && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(), - mCurrentUserId); - dismissKeyguardThenExecute(new OnDismissAction() { - @Override - public boolean onDismiss() { - if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) { - // Release the HUN notification to the shade. - - if (isPanelFullyCollapsed()) { - HeadsUpManager.setIsClickedNotification(row, true); - } - // - // In most cases, when FLAG_AUTO_CANCEL is set, the notification will - // become canceled shortly by NoMan, but we can't assume that. - mHeadsUpManager.releaseImmediately(notificationKey); - } - StatusBarNotification parentToCancel = null; - if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) { - StatusBarNotification summarySbn = mGroupManager.getLogicalGroupSummary(sbn) - .getStatusBarNotification(); - if (shouldAutoCancel(summarySbn)) { - parentToCancel = summarySbn; - } - } - final StatusBarNotification parentToCancelFinal = parentToCancel; - final Runnable runnable = new Runnable() { - @Override - public void run() { - try { - // The intent we are sending is for the application, which - // won't have permission to immediately start an activity after - // the user switches to home. We know it is safe to do at this - // point, so make sure new activity switches are now allowed. - ActivityManager.getService().resumeAppSwitches(); - } catch (RemoteException e) { - } - if (intent != null) { - // If we are launching a work activity and require to launch - // separate work challenge, we defer the activity action and cancel - // notification until work challenge is unlocked. - if (intent.isActivity()) { - final int userId = intent.getCreatorUserHandle() - .getIdentifier(); - if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId) - && mKeyguardManager.isDeviceLocked(userId)) { - // TODO(b/28935539): should allow certain activities to - // bypass work challenge - if (startWorkChallengeIfNecessary(userId, - intent.getIntentSender(), notificationKey)) { - // Show work challenge, do not run PendingIntent and - // remove notification - return; - } - } - } - try { - intent.send(null, 0, null, null, null, null, - getActivityOptions()); - } catch (PendingIntent.CanceledException e) { - // the stack trace isn't very helpful here. - // Just log the exception message. - Log.w(TAG, "Sending contentIntent failed: " + e); - - // TODO: Dismiss Keyguard. - } - if (intent.isActivity()) { - mAssistManager.hideAssist(); - } - } - - try { - mBarService.onNotificationClick(notificationKey); - } catch (RemoteException ex) { - // system process is dead if we're here. - } - if (parentToCancelFinal != null) { - // We have to post it to the UI thread for synchronization - mHandler.post(new Runnable() { - @Override - public void run() { - Runnable removeRunnable = new Runnable() { - @Override - public void run() { - performRemoveNotification(parentToCancelFinal); - } - }; - if (isCollapsing()) { - // To avoid lags we're only performing the remove - // after the shade was collapsed - addPostCollapseAction(removeRunnable); - } else { - removeRunnable.run(); - } - } - }); - } - } - }; - - if (mStatusBarKeyguardViewManager.isShowing() - && mStatusBarKeyguardViewManager.isOccluded()) { - mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable); - } else { - new Thread(runnable).start(); - } - - if (!mNotificationPanel.isFullyCollapsed()) { - // close the shade if it was open - animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, - true /* force */, true /* delayed */); - visibilityChanged(false); - - return true; - } else { - return false; - } + if (intent.isActivity()) { + mAssistManager.hideAssist(); } - }, afterKeyguardGone); - } + }).start(); - private boolean shouldAutoCancel(StatusBarNotification sbn) { - int flags = sbn.getNotification().flags; - if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) { - return false; - } - if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { - return false; - } - return true; - } + return collapsePanel(); + }, afterKeyguardGone); + } - public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { - Notification notification = sbn.getNotification(); - if (notification.contentIntent != null || notification.fullScreenIntent != null) { - row.setOnClickListener(this); - } else { - row.setOnClickListener(null); - } + private boolean shouldAutoCancel(StatusBarNotification sbn) { + int flags = sbn.getNotification().flags; + if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) { + return false; } + if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { + return false; + } + return true; } - protected Bundle getActivityOptions() { - // Anything launched from the notification shade should always go into the - // fullscreen stack. - ActivityOptions options = ActivityOptions.makeBasic(); - options.setLaunchStackId(StackId.FULLSCREEN_WORKSPACE_STACK_ID); + protected Bundle getActivityOptions(ExpandableNotificationRow sourceNotification) { + ActivityOptions options; + if (sourceNotification != null) { + options = mActivityLaunchAnimator.getLaunchAnimation(sourceNotification); + } else { + options = ActivityOptions.makeBasic(); + } + // Anything launched from the notification shade should always go into the secondary + // split-screen windowing mode. + options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY); return options.toBundle(); } @@ -7164,7 +5345,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (mVisible != visible) { mVisible = visible; if (!visible) { - closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, + mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */); } } @@ -7192,125 +5373,10 @@ public class StatusBar extends SystemUI implements DemoMode, } /** - * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService - * about the failure. - * - * WARNING: this will call back into us. Don't hold any locks. - */ - void handleNotificationError(StatusBarNotification n, String message) { - removeNotification(n.getKey(), null); - try { - mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), - n.getInitialPid(), message, n.getUserId()); - } catch (RemoteException ex) { - // The end is nigh. - } - } - - protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) { - NotificationData.Entry entry = mNotificationData.remove(key, ranking); - if (entry == null) { - Log.w(TAG, "removeNotification for unknown key: " + key); - return null; - } - updateNotifications(); - Dependency.get(LeakDetector.class).trackGarbage(entry); - return entry.notification; - } - - protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) - throws InflationException { - if (DEBUG) { - Log.d(TAG, "createNotificationViews(notification=" + sbn); - } - NotificationData.Entry entry = new NotificationData.Entry(sbn); - Dependency.get(LeakDetector.class).trackInstance(entry); - entry.createIcons(mContext, sbn); - // Construct the expanded view. - inflateViews(entry, mStackScroller); - return entry; - } - - protected void addNotificationViews(Entry entry) { - if (entry == null) { - return; - } - // Add the expanded view and icon. - mNotificationData.add(entry); - updateNotifications(); - } - - /** * Updates expanded, dimmed and locked states of notification rows. */ - protected void updateRowStates() { - final int N = mStackScroller.getChildCount(); - - int visibleNotifications = 0; - boolean onKeyguard = mState == StatusBarState.KEYGUARD; - int maxNotifications = -1; - if (onKeyguard) { - maxNotifications = getMaxKeyguardNotifications(true /* recompute */); - } - mStackScroller.setMaxDisplayedNotifications(maxNotifications); - Stack<ExpandableNotificationRow> stack = new Stack<>(); - for (int i = N - 1; i >= 0; i--) { - View child = mStackScroller.getChildAt(i); - if (!(child instanceof ExpandableNotificationRow)) { - continue; - } - stack.push((ExpandableNotificationRow) child); - } - while(!stack.isEmpty()) { - ExpandableNotificationRow row = stack.pop(); - NotificationData.Entry entry = row.getEntry(); - boolean isChildNotification = - mGroupManager.isChildInGroupWithSummary(entry.notification); - - row.setOnKeyguard(onKeyguard); - - if (!onKeyguard) { - // If mAlwaysExpandNonGroupedNotification is false, then only expand the - // very first notification and if it's not a child of grouped notifications. - row.setSystemExpanded(mAlwaysExpandNonGroupedNotification - || (visibleNotifications == 0 && !isChildNotification - && !row.isLowPriority())); - } - - entry.row.setShowAmbient(isDozing()); - int userId = entry.notification.getUserId(); - boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup( - entry.notification) && !entry.row.isRemoved(); - boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification); - if (suppressedSummary - || (isLockscreenPublicMode(userId) && !mShowLockscreenNotifications) - || (onKeyguard && !showOnKeyguard)) { - entry.row.setVisibility(View.GONE); - } else { - boolean wasGone = entry.row.getVisibility() == View.GONE; - if (wasGone) { - entry.row.setVisibility(View.VISIBLE); - } - if (!isChildNotification && !entry.row.isRemoved()) { - if (wasGone) { - // notify the scroller of a child addition - mStackScroller.generateAddAnimation(entry.row, - !showOnKeyguard /* fromMoreCard */); - } - visibleNotifications++; - } - } - if (row.isSummaryWithChildren()) { - List<ExpandableNotificationRow> notificationChildren = - row.getNotificationChildren(); - int size = notificationChildren.size(); - for (int i = size - 1; i >= 0; i--) { - stack.push(notificationChildren.get(i)); - } - } - } - mNotificationPanel.setNoVisibleNotifications(visibleNotifications == 0); - + @Override + public void onUpdateRowStates() { // The following views will be moved to the end of mStackScroller. This counter represents // the offset from the last child. Initialized to 1 for the very last position. It is post- // incremented in the following "changeViewPosition" calls so that its value is correct for @@ -7333,180 +5399,10 @@ public class StatusBar extends SystemUI implements DemoMode, mScrimController.setNotificationCount(mStackScroller.getNotGoneChildCount()); } - public boolean shouldShowOnKeyguard(StatusBarNotification sbn) { - return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey()); - } - - // extended in StatusBar - protected void setShowLockscreenNotifications(boolean show) { - mShowLockscreenNotifications = show; - } - - protected void setLockScreenAllowRemoteInput(boolean allowLockscreenRemoteInput) { - mAllowLockscreenRemoteInput = allowLockscreenRemoteInput; - } - - private void updateLockscreenNotificationSetting() { - final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, - 1, - mCurrentUserId) != 0; - final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( - null /* admin */, mCurrentUserId); - final boolean allowedByDpm = (dpmFlags - & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; - - setShowLockscreenNotifications(show && allowedByDpm); - - if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) { - final boolean remoteInput = Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, - 0, - mCurrentUserId) != 0; - final boolean remoteInputDpm = - (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0; - - setLockScreenAllowRemoteInput(remoteInput && remoteInputDpm); - } else { - setLockScreenAllowRemoteInput(false); - } - } - - public void updateNotification(StatusBarNotification notification, RankingMap ranking) - throws InflationException { - if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); - - final String key = notification.getKey(); - abortExistingInflation(key); - Entry entry = mNotificationData.get(key); - if (entry == null) { - return; - } - mHeadsUpEntriesToRemoveOnSwitch.remove(entry); - mRemoteInputEntriesToRemoveOnCollapse.remove(entry); - if (key.equals(mKeyToRemoveOnGutsClosed)) { - mKeyToRemoveOnGutsClosed = null; - Log.w(TAG, "Notification that was kept for guts was updated. " + key); - } - - Notification n = notification.getNotification(); - mNotificationData.updateRanking(ranking); - - final StatusBarNotification oldNotification = entry.notification; - entry.notification = notification; - mGroupManager.onEntryUpdated(entry, oldNotification); - - entry.updateIcons(mContext, notification); - inflateViews(entry, mStackScroller); - - mForegroundServiceController.updateNotification(notification, - mNotificationData.getImportance(key)); - - boolean shouldPeek = shouldPeek(entry, notification); - boolean alertAgain = alertAgain(entry, n); - - updateHeadsUp(key, entry, shouldPeek, alertAgain); - updateNotifications(); - - if (!notification.isClearable()) { - // The user may have performed a dismiss action on the notification, since it's - // not clearable we should snap it back. - mStackScroller.snapViewIfNeeded(entry.row); - } - - if (DEBUG) { - // Is this for you? - boolean isForCurrentUser = isNotificationForCurrentProfiles(notification); - Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); - } - - setAreThereNotifications(); - } - protected void notifyHeadsUpGoingToSleep() { maybeEscalateHeadsUp(); } - private boolean alertAgain(Entry oldEntry, Notification newNotification) { - return oldEntry == null || !oldEntry.hasInterrupted() - || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; - } - - protected boolean shouldPeek(Entry entry) { - return shouldPeek(entry, entry.notification); - } - - protected boolean shouldPeek(Entry entry, StatusBarNotification sbn) { - if (!mUseHeadsUp || isDeviceInVrMode()) { - if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode"); - return false; - } - - if (mNotificationData.shouldFilterOut(sbn)) { - if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey()); - return false; - } - - boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming(); - - if (!inUse && !isDozing()) { - if (DEBUG) { - Log.d(TAG, "No peeking: not in use: " + sbn.getKey()); - } - return false; - } - - if (!isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) { - if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); - return false; - } - - if (isDozing() && mNotificationData.shouldSuppressScreenOff(sbn.getKey())) { - if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); - return false; - } - - if (entry.hasJustLaunchedFullScreenIntent()) { - if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey()); - return false; - } - - if (isSnoozedPackage(sbn)) { - if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey()); - return false; - } - - // Allow peeking for DEFAULT notifications only if we're on Ambient Display. - int importanceLevel = isDozing() ? NotificationManager.IMPORTANCE_DEFAULT - : NotificationManager.IMPORTANCE_HIGH; - if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) { - if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey()); - return false; - } - - if (sbn.getNotification().fullScreenIntent != null) { - if (mAccessibilityManager.isTouchExplorationEnabled()) { - if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey()); - return false; - } else if (mDozing) { - // We never want heads up when we are dozing. - return false; - } else { - // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent - return !mStatusBarKeyguardViewManager.isShowing() - || mStatusBarKeyguardViewManager.isOccluded(); - } - } - - // Don't peek notifications that are suppressed due to group alert behavior - if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) { - if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior"); - return false; - } - - return true; - } - /** * @return Whether the security bouncer from Keyguard is showing. */ @@ -7536,17 +5432,6 @@ public class StatusBar extends SystemUI implements DemoMode, return contextForUser.getPackageManager(); } - @Override - public void logNotificationExpansion(String key, boolean userAction, boolean expanded) { - mUiOffloadThread.submit(() -> { - try { - mBarService.onNotificationExpansionChanged(key, userAction, expanded); - } catch (RemoteException e) { - // Ignore. - } - }); - } - public boolean isKeyguardSecure() { if (mStatusBarKeyguardViewManager == null) { // startKeyguard() hasn't been called yet, so we don't know. @@ -7583,4 +5468,33 @@ public class StatusBar extends SystemUI implements DemoMode, mNavigationBar.getBarTransitions().setAutoDim(true); } }; + + public NotificationGutsManager getGutsManager() { + return mGutsManager; + } + + @Override + public boolean isPresenterLocked() { + return mState == StatusBarState.KEYGUARD; + } + + @Override + public Handler getHandler() { + return mHandler; + } + + private final NotificationInfo.CheckSaveListener mCheckSaveListener = + (Runnable saveImportance, StatusBarNotification sbn) -> { + // If the user has security enabled, show challenge if the setting is changed. + if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier()) + && (mState == StatusBarState.KEYGUARD || + mState == StatusBarState.SHADE_LOCKED)) { + onLockedNotificationImportanceChange(() -> { + saveImportance.run(); + return true; + }); + } else { + saveImportance.run(); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index c2407652b2df..07610ceff7b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -14,10 +14,10 @@ package com.android.systemui.statusbar.phone; -import android.annotation.ColorInt; +import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS; +import static android.app.StatusBarManager.DISABLE_NONE; + import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Color; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.ArraySet; @@ -29,11 +29,11 @@ import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; import com.android.internal.statusbar.StatusBarIcon; -import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.policy.DarkIconDispatcher; +import com.android.systemui.util.Utils.DisableStateTracker; public interface StatusBarIconController { @@ -62,7 +62,7 @@ public interface StatusBarIconController { } /** - * Version of ViewGroup that observers state from the DarkIconDispatcher. + * Version of ViewGroup that observes state from the DarkIconDispatcher. */ public static class DarkIconManager extends IconManager { private final DarkIconDispatcher mDarkIconDispatcher; @@ -149,6 +149,14 @@ public interface StatusBarIconController { mContext = group.getContext(); mIconSize = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.status_bar_icon_size); + + DisableStateTracker tracker = + new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS); + mGroup.addOnAttachStateChangeListener(tracker); + if (mGroup.isAttachedToWindow()) { + // In case we miss the first onAttachedToWindow event + tracker.onViewAttachedToWindow(mGroup); + } } protected void onIconAdded(int index, String slot, boolean blocked, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java index 68f8e065a429..1c3ee758a3ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java @@ -33,7 +33,6 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; -import com.android.systemui.statusbar.policy.DarkIconDispatcher; import com.android.systemui.statusbar.policy.IconLogger; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -50,21 +49,17 @@ import java.util.ArrayList; public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable, ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController { - private final DarkIconDispatcher mDarkIconDispatcher; - - private Context mContext; - private DemoStatusIcons mDemoStatusIcons; - private final ArrayList<IconManager> mIconGroups = new ArrayList<>(); - private final ArraySet<String> mIconBlacklist = new ArraySet<>(); private final IconLogger mIconLogger = Dependency.get(IconLogger.class); + private Context mContext; + private DemoStatusIcons mDemoStatusIcons; + public StatusBarIconControllerImpl(Context context) { super(context.getResources().getStringArray( com.android.internal.R.array.config_statusBarIcons)); Dependency.get(ConfigurationController.class).addCallback(this); - mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class); mContext = context; loadDimens(); 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 bbce751dcdfd..c6673095d932 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -24,7 +24,6 @@ import android.content.ComponentCallbacks2; import android.content.Context; import android.os.Bundle; import android.os.SystemClock; -import android.os.Trace; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; @@ -34,7 +33,7 @@ import android.view.WindowManagerGlobal; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; -import com.android.keyguard.LatencyTracker; +import com.android.internal.util.LatencyTracker; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.DejankUtils; import com.android.systemui.Dependency; @@ -71,17 +70,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb protected final Context mContext; private final StatusBarWindowManager mStatusBarWindowManager; - private final boolean mDisplayBlanksAfterDoze; protected LockPatternUtils mLockPatternUtils; protected ViewMediatorCallback mViewMediatorCallback; protected StatusBar mStatusBar; - private ScrimController mScrimController; private FingerprintUnlockController mFingerprintUnlockController; private ViewGroup mContainer; - private boolean mScreenTurnedOn; protected KeyguardBouncer mBouncer; protected boolean mShowing; protected boolean mOccluded; @@ -95,12 +91,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private boolean mLastBouncerDismissible; protected boolean mLastRemoteInputActive; private boolean mLastDozing; - private boolean mLastDeferScrimFadeOut; private int mLastFpMode; private OnDismissAction mAfterKeyguardGoneAction; private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>(); - private boolean mDeferScrimFadeOut; // Dismiss action to be launched when we stop dozing or the keyguard is gone. private DismissWithActionRequest mPendingWakeupAction; @@ -125,18 +119,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mLockPatternUtils = lockPatternUtils; mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class); KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback); - mDisplayBlanksAfterDoze = context.getResources().getBoolean( - com.android.internal.R.bool.config_displayBlanksAfterDoze); } public void registerStatusBar(StatusBar statusBar, ViewGroup container, - ScrimController scrimController, FingerprintUnlockController fingerprintUnlockController, DismissCallbackRegistry dismissCallbackRegistry) { mStatusBar = statusBar; mContainer = container; - mScrimController = scrimController; mFingerprintUnlockController = fingerprintUnlockController; mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext, mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry); @@ -149,7 +139,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void show(Bundle options) { mShowing = true; mStatusBarWindowManager.setKeyguardShowing(true); - mScrimController.abortKeyguardFadingOut(); reset(true /* hideBouncerWhenShowing */); } @@ -186,13 +175,18 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void dismissWithAction(OnDismissAction r, Runnable cancelAction, boolean afterKeyguardGone) { + dismissWithAction(r, cancelAction, afterKeyguardGone, null /* message */); + } + + public void dismissWithAction(OnDismissAction r, Runnable cancelAction, + boolean afterKeyguardGone, String message) { if (mShowing) { cancelPendingWakeupAction(); // If we're dozing, this needs to be delayed until after we wake up - unless we're // wake-and-unlocking, because there dozing will last until the end of the transition. if (mDozing && !isWakeAndUnlocking()) { mPendingWakeupAction = new DismissWithActionRequest( - r, cancelAction, afterKeyguardGone); + r, cancelAction, afterKeyguardGone, message); return; } @@ -225,7 +219,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (mShowing) { if (mOccluded && !mDozing) { mStatusBar.hideKeyguard(); - mStatusBar.stopWaitingForKeyguardExit(); if (hideBouncerWhenShowing || mBouncer.needsFullscreenBouncer()) { hideBouncer(false /* destroyView */); } @@ -254,15 +247,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public void onScreenTurnedOn() { - Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurnedOn"); - mScreenTurnedOn = true; - if (mDeferScrimFadeOut) { - mDeferScrimFadeOut = false; - animateScrimControllerKeyguardFadingOut(0, WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS, - true /* skipFirstFrame */); - updateStates(); - } - Trace.endSection(); + // TODO: remove } @Override @@ -286,7 +271,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public void onScreenTurnedOff() { - mScreenTurnedOn = false; + // TODO: remove } public void notifyDeviceWakeUpRequested() { @@ -375,10 +360,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mStatusBarWindowManager.setKeyguardFadingAway(true); hideBouncer(true /* destroyView */); updateStates(); - mScrimController.animateKeyguardFadingOut( - StatusBar.FADE_KEYGUARD_START_DELAY, - StatusBar.FADE_KEYGUARD_DURATION, null, - false /* skipFirstFrame */); } }, new Runnable() { @Override @@ -401,36 +382,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mFingerprintUnlockController.startKeyguardFadingAway(); hideBouncer(true /* destroyView */); if (wakeUnlockPulsing) { - mStatusBarWindowManager.setKeyguardFadingAway(true); mStatusBar.fadeKeyguardWhilePulsing(); - animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration, - mStatusBar::hideKeyguard, false /* skipFirstFrame */); + wakeAndUnlockDejank(); } else { - mFingerprintUnlockController.startKeyguardFadingAway(); - mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration); boolean staying = mStatusBar.hideKeyguard(); if (!staying) { mStatusBarWindowManager.setKeyguardFadingAway(true); - if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) { - boolean turnedOnSinceAuth = - mFingerprintUnlockController.hasScreenTurnedOnSinceAuthenticating(); - if (!mScreenTurnedOn || mDisplayBlanksAfterDoze && !turnedOnSinceAuth) { - // Not ready to animate yet; either because the screen is not on yet, - // or it is on but will turn off before waking out of doze. - mDeferScrimFadeOut = true; - } else { - - // Screen is already on, don't defer with fading out. - animateScrimControllerKeyguardFadingOut(0, - WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS, - true /* skipFirstFrame */); - } - } else { - animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration, - false /* skipFirstFrame */); - } + wakeAndUnlockDejank(); } else { - mScrimController.animateGoingToFullShade(delay, fadeoutDuration); mStatusBar.finishKeyguardFadingAway(); mFingerprintUnlockController.finishKeyguardFadingAway(); } @@ -445,35 +404,22 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb hideBouncer(true /* destroyView */); } - public void onOverlayChanged() { + public void onThemeChanged() { hideBouncer(true /* destroyView */); mBouncer.prepare(); } - private void animateScrimControllerKeyguardFadingOut(long delay, long duration, - boolean skipFirstFrame) { - animateScrimControllerKeyguardFadingOut(delay, duration, null /* endRunnable */, - skipFirstFrame); + public void onKeyguardFadedAway() { + mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false), + 100); + mStatusBar.finishKeyguardFadingAway(); + mFingerprintUnlockController.finishKeyguardFadingAway(); + WindowManagerGlobal.getInstance().trimMemory( + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); + } - private void animateScrimControllerKeyguardFadingOut(long delay, long duration, - final Runnable endRunnable, boolean skipFirstFrame) { - Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "Fading out", 0); - mScrimController.animateKeyguardFadingOut(delay, duration, new Runnable() { - @Override - public void run() { - if (endRunnable != null) { - endRunnable.run(); - } - mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false), - 100); - mStatusBar.finishKeyguardFadingAway(); - mFingerprintUnlockController.finishKeyguardFadingAway(); - WindowManagerGlobal.getInstance().trimMemory( - ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); - Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "Fading out", 0); - } - }, skipFirstFrame); + private void wakeAndUnlockDejank() { if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK && LatencyTracker.isEnabled(mContext)) { DejankUtils.postAfterTraversal(() -> @@ -594,7 +540,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) { mStatusBarWindowManager.setBouncerShowing(bouncerShowing); mStatusBar.setBouncerShowing(bouncerShowing); - mScrimController.setBouncerShowing(bouncerShowing); } KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); @@ -612,7 +557,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mLastBouncerDismissible = bouncerDismissible; mLastRemoteInputActive = remoteInputActive; mLastDozing = mDozing; - mLastDeferScrimFadeOut = mDeferScrimFadeOut; mLastFpMode = mFingerprintUnlockController.getMode(); mStatusBar.onKeyguardViewManagerStatesUpdated(); } @@ -625,7 +569,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb boolean keyguardShowing = mShowing && !mOccluded; boolean hideWhileDozing = mDozing && fpMode != MODE_WAKE_AND_UNLOCK_PULSING; return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing() - || mRemoteInputActive) && !mDeferScrimFadeOut; + || mRemoteInputActive); } /** @@ -635,7 +579,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb boolean keyguardShowing = mLastShowing && !mLastOccluded; boolean hideWhileDozing = mLastDozing && mLastFpMode != MODE_WAKE_AND_UNLOCK_PULSING; return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing - || mLastRemoteInputActive) && !mLastDeferScrimFadeOut; + || mLastRemoteInputActive); } public boolean shouldDismissOnMenuPressed() { @@ -693,7 +637,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (request != null) { if (mShowing) { dismissWithAction(request.dismissAction, request.cancelAction, - request.afterKeyguardGone); + request.afterKeyguardGone, request.message); } else if (request.dismissAction != null) { request.dismissAction.onDismiss(); } @@ -712,12 +656,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb final OnDismissAction dismissAction; final Runnable cancelAction; final boolean afterKeyguardGone; + final String message; DismissWithActionRequest(OnDismissAction dismissAction, Runnable cancelAction, - boolean afterKeyguardGone) { + boolean afterKeyguardGone, String message) { this.dismissAction = dismissAction; this.cancelAction = cancelAction; this.afterKeyguardGone = afterKeyguardGone; + this.message = message; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java index ed96b4115888..948f524bb188 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; + import android.app.ActivityManager; import android.app.IActivityManager; import android.content.Context; @@ -52,6 +54,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D private final Context mContext; private final WindowManager mWindowManager; private final IActivityManager mActivityManager; + private final DozeParameters mDozeParameters; private View mStatusBarView; private WindowManager.LayoutParams mLp; private WindowManager.LayoutParams mLpChanged; @@ -68,8 +71,8 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mActivityManager = ActivityManager.getService(); mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation(); - mScreenBrightnessDoze = mContext.getResources().getInteger( - com.android.internal.R.integer.config_screenBrightnessDoze) / 255f; + mDozeParameters = new DozeParameters(mContext); + mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze(); } private boolean shouldEnableKeyguardScreenRotation() { @@ -103,6 +106,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D mLp.gravity = Gravity.TOP; mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; mLp.setTitle("StatusBar"); + mLp.accessibilityTitle = mContext.getString(R.string.status_bar); mLp.packageName = mContext.getPackageName(); mStatusBarView = statusBarView; mBarHeight = barHeight; @@ -134,7 +138,11 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D mLpChanged.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; } - if (state.keyguardShowing && !state.backdropShowing && !state.dozing) { + final boolean showWallpaperOnAod = mDozeParameters.getAlwaysOn() && + state.wallpaperSupportsAmbientMode && + state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_OPAQUE; + if (state.keyguardShowing && !state.backdropShowing && + (!state.dozing || showWallpaperOnAod)) { mLpChanged.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; } else { mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; @@ -156,7 +164,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D private void applyFocusableFlag(State state) { boolean panelFocusable = state.statusBarFocusable && state.panelExpanded; if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput) - || StatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) { + || ENABLE_REMOTE_INPUT && state.remoteInputActive) { mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) { @@ -186,7 +194,8 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D private boolean isExpanded(State state) { return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded() || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing - || state.headsUpShowing || state.scrimsVisible); + || state.headsUpShowing + || state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_TRANSPARENT); } private void applyFitsSystemWindows(State state) { @@ -334,8 +343,8 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D apply(mCurrentState); } - public void setScrimsVisible(boolean scrimsVisible) { - mCurrentState.scrimsVisible = scrimsVisible; + public void setScrimsVisibility(int scrimsVisibility) { + mCurrentState.scrimsVisibility = scrimsVisibility; apply(mCurrentState); } @@ -344,6 +353,11 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D apply(mCurrentState); } + public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) { + mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode; + apply(mCurrentState); + } + /** * @param state The {@link StatusBarState} of the status bar. */ @@ -431,6 +445,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D boolean forceDozeBrightness; boolean forceUserActivity; boolean backdropShowing; + boolean wallpaperSupportsAmbientMode; /** * The {@link StatusBar} state from the status bar. @@ -440,7 +455,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D boolean remoteInputActive; boolean forcePluginOpen; boolean dozing; - boolean scrimsVisible; + int scrimsVisibility; private boolean isKeyguardShowingAndNotOccluded() { return keyguardShowing && !keyguardOccluded; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index d7f11f710501..a79a41b07797 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -62,6 +62,9 @@ import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; +import java.io.FileDescriptor; +import java.io.PrintWriter; + public class StatusBarWindowView extends FrameLayout { public static final String TAG = "StatusBarWindowView"; @@ -88,6 +91,8 @@ public class StatusBarWindowView extends FrameLayout { private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener; private boolean mTouchCancelled; private boolean mTouchActive; + private boolean mExpandAnimationRunning; + private boolean mExpandAnimationPending; public StatusBarWindowView(Context context, AttributeSet attrs) { super(context, attrs); @@ -248,7 +253,6 @@ public class StatusBarWindowView extends FrameLayout { public void setTouchActive(boolean touchActive) { mTouchActive = touchActive; - mStackScrollLayout.setTouchActive(touchActive); } @Override @@ -268,7 +272,7 @@ public class StatusBarWindowView extends FrameLayout { || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) { setTouchActive(false); } - if (mTouchCancelled) { + if (mTouchCancelled || mExpandAnimationRunning || mExpandAnimationPending) { return false; } mFalsingManager.onTouchEvent(ev, getWidth(), getHeight()); @@ -389,6 +393,21 @@ public class StatusBarWindowView extends FrameLayout { } } + public void setExpandAnimationRunning(boolean expandAnimationRunning) { + mExpandAnimationRunning = expandAnimationRunning; + } + + public void setExpandAnimationPending(boolean pending) { + mExpandAnimationPending = pending; + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.print(" mExpandAnimationPending="); pw.println(mExpandAnimationPending); + pw.print(" mExpandAnimationRunning="); pw.println(mExpandAnimationRunning); + pw.print(" mTouchCancelled="); pw.println(mTouchCancelled); + pw.print(" mTouchActive="); pw.println(mTouchActive); + } + public class LayoutParams extends FrameLayout.LayoutParams { public boolean ignoreRightInset; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java new file mode 100644 index 000000000000..503a1b40bb0c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java @@ -0,0 +1,180 @@ +/* + * 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. + */ + +/** + * A container for Status bar system icons. Limits the number of system icons and handles overflow + * similar to NotificationIconController. Can be used to layout nested StatusIconContainers + * + * Children are expected to be of type StatusBarIconView. + */ +package com.android.systemui.statusbar.phone; + +import android.annotation.Nullable; +import android.content.Context; +import android.util.ArrayMap; +import android.util.AttributeSet; + +import android.view.View; +import com.android.keyguard.AlphaOptimizedLinearLayout; +import com.android.systemui.R; +import com.android.systemui.statusbar.StatusBarIconView; +import com.android.systemui.statusbar.stack.ViewState; + +public class StatusIconContainer extends AlphaOptimizedLinearLayout { + + private static final String TAG = "StatusIconContainer"; + private static final boolean DEBUG = false; + private static final int MAX_ICONS = 5; + private static final int MAX_DOTS = 3; + + public StatusIconContainer(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + float midY = getHeight() / 2.0f; + + // Layout all child views so that we can move them around later + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + int width = child.getMeasuredWidth(); + int height = child.getMeasuredHeight(); + int top = (int) (midY - height / 2.0f); + child.layout(0, top, width, top + height); + } + + resetViewStates(); + calculateIconTranslations(); + applyIconStates(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + final int count = getChildCount(); + // Measure all children so that they report the correct width + for (int i = 0; i < count; i++) { + measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec); + } + } + + @Override + public void onViewAdded(View child) { + super.onViewAdded(child); + ViewState vs = new ViewState(); + child.setTag(R.id.status_bar_view_state_tag, vs); + } + + @Override + public void onViewRemoved(View child) { + super.onViewRemoved(child); + child.setTag(R.id.status_bar_view_state_tag, null); + } + + /** + * Layout is happening from end -> start + */ + private void calculateIconTranslations() { + float width = getWidth(); + float translationX = width; + float contentStart = getPaddingStart(); + int childCount = getChildCount(); + // Underflow === don't show content until that index + int firstUnderflowIndex = -1; + if (DEBUG) android.util.Log.d(TAG, "calculateIconTransitions: start=" + translationX); + + //TODO: Dots + for (int i = childCount - 1; i >= 0; i--) { + View child = getChildAt(i); + if (!(child instanceof StatusBarIconView)) { + continue; + } + + ViewState childState = getViewStateFromChild(child); + if (childState == null ) { + continue; + } + + // Rely on StatusBarIcon for truth about visibility + if (!((StatusBarIconView) child).getStatusBarIcon().visible) { + childState.hidden = true; + continue; + } + + childState.xTranslation = translationX - child.getWidth(); + + if (childState.xTranslation < contentStart) { + if (firstUnderflowIndex == -1) { + firstUnderflowIndex = i; + } + } + + translationX -= child.getWidth(); + } + + if (firstUnderflowIndex != -1) { + for (int i = 0; i <= firstUnderflowIndex; i++) { + View child = getChildAt(i); + ViewState vs = getViewStateFromChild(child); + if (vs != null) { + vs.hidden = true; + } + } + } + + // Stole this from NotificationIconContainer. Not optimal but keeps the layout logic clean + if (isLayoutRtl()) { + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + ViewState state = getViewStateFromChild(child); + state.xTranslation = width - state.xTranslation - child.getWidth(); + } + } + } + + private void applyIconStates() { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + ViewState vs = getViewStateFromChild(child); + if (vs != null) { + vs.applyToView(child); + } + } + } + + private void resetViewStates() { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + ViewState vs = getViewStateFromChild(child); + if (vs == null) { + continue; + } + + vs.initFrom(child); + vs.alpha = 1.0f; + if (child instanceof StatusBarIconView) { + vs.hidden = !((StatusBarIconView)child).getStatusBarIcon().visible; + } else { + vs.hidden = false; + } + } + } + + private static @Nullable ViewState getViewStateFromChild(View child) { + return (ViewState) child.getTag(R.id.status_bar_view_state_tag); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java index c0a683734b8b..0d21c4efe22e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java @@ -20,7 +20,6 @@ import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import android.net.wifi.WifiManager.ActionListener; -import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -59,13 +58,19 @@ public class AccessPointControllerImpl private int mCurrentUser; - public AccessPointControllerImpl(Context context, Looper bgLooper) { + public AccessPointControllerImpl(Context context) { mContext = context; mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - mWifiTracker = new WifiTracker(context, this, bgLooper, false, true); + mWifiTracker = new WifiTracker(context, this, false, true); mCurrentUser = ActivityManager.getCurrentUser(); } + @Override + protected void finalize() throws Throwable { + super.finalize(); + mWifiTracker.onDestroy(); + } + public boolean canConfigWifi() { return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, new UserHandle(mCurrentUser)); @@ -81,7 +86,7 @@ public class AccessPointControllerImpl if (DEBUG) Log.d(TAG, "addCallback " + callback); mCallbacks.add(callback); if (mCallbacks.size() == 1) { - mWifiTracker.startTracking(); + mWifiTracker.onStart(); } } @@ -91,7 +96,7 @@ public class AccessPointControllerImpl if (DEBUG) Log.d(TAG, "removeCallback " + callback); mCallbacks.remove(callback); if (mCallbacks.isEmpty()) { - mWifiTracker.stopTracking(); + mWifiTracker.onStop(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java index 6a573f593a92..d85e18c17252 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java @@ -14,10 +14,14 @@ package com.android.systemui.statusbar.policy; +import android.accessibilityservice.AccessibilityServiceInfo; import android.content.Context; +import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; +import java.util.List; + /** * For mocking because AccessibilityManager is final for some reason... */ @@ -39,4 +43,27 @@ public class AccessibilityManagerWrapper implements public void removeCallback(AccessibilityServicesStateChangeListener listener) { mAccessibilityManager.removeAccessibilityServicesStateChangeListener(listener); } + + public void addAccessibilityStateChangeListener( + AccessibilityManager.AccessibilityStateChangeListener listener) { + mAccessibilityManager.addAccessibilityStateChangeListener(listener); + } + + public void removeAccessibilityStateChangeListener( + AccessibilityManager.AccessibilityStateChangeListener listener) { + mAccessibilityManager.removeAccessibilityStateChangeListener(listener); + } + + public boolean isEnabled() { + return mAccessibilityManager.isEnabled(); + } + + public void sendAccessibilityEvent(AccessibilityEvent event) { + mAccessibilityManager.sendAccessibilityEvent(event); + } + + public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( + int feedbackTypeFlags) { + return mAccessibilityManager.getEnabledAccessibilityServiceList(feedbackTypeFlags); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java index 42ce4c5bf2dc..a011952f1476 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java @@ -16,7 +16,9 @@ package com.android.systemui.statusbar.policy; +import android.annotation.NonNull; import android.util.ArraySet; +import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewPropertyAnimator; @@ -25,55 +27,52 @@ import android.widget.FrameLayout; import com.android.internal.util.Preconditions; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBarWindowView; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; +import java.util.function.Consumer; + /** * Controls showing and hiding of the brightness mirror. */ public class BrightnessMirrorController implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> { - private final NotificationStackScrollLayout mStackScroller; - public long TRANSITION_DURATION_OUT = 150; - public long TRANSITION_DURATION_IN = 200; + private final static long TRANSITION_DURATION_OUT = 150; + private final static long TRANSITION_DURATION_IN = 200; private final StatusBarWindowView mStatusBarWindow; - private final ScrimController mScrimController; + private final NotificationStackScrollLayout mStackScroller; + private final Consumer<Boolean> mVisibilityCallback; private final View mNotificationPanel; private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>(); private final int[] mInt2Cache = new int[2]; private View mBrightnessMirror; public BrightnessMirrorController(StatusBarWindowView statusBarWindow, - ScrimController scrimController) { + @NonNull Consumer<Boolean> visibilityCallback) { mStatusBarWindow = statusBarWindow; mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror); mNotificationPanel = statusBarWindow.findViewById(R.id.notification_panel); - mStackScroller = (NotificationStackScrollLayout) statusBarWindow.findViewById( - R.id.notification_stack_scroller); - mScrimController = scrimController; + mStackScroller = statusBarWindow.findViewById(R.id.notification_stack_scroller); + mVisibilityCallback = visibilityCallback; } public void showMirror() { mBrightnessMirror.setVisibility(View.VISIBLE); mStackScroller.setFadingOut(true); - mScrimController.forceHideScrims(true /* hide */, true /* animated */); + mVisibilityCallback.accept(true); outAnimation(mNotificationPanel.animate()) .withLayer(); } public void hideMirror() { - mScrimController.forceHideScrims(false /* hide */, true /* animated */); + mVisibilityCallback.accept(false); inAnimation(mNotificationPanel.animate()) .withLayer() - .withEndAction(new Runnable() { - @Override - public void run() { - mBrightnessMirror.setVisibility(View.INVISIBLE); - mStackScroller.setFadingOut(false); - } + .withEndAction(() -> { + mBrightnessMirror.setVisibility(View.INVISIBLE); + mStackScroller.setFadingOut(false); }); } @@ -128,9 +127,11 @@ public class BrightnessMirrorController } private void reinflate() { + ContextThemeWrapper qsThemeContext = + new ContextThemeWrapper(mBrightnessMirror.getContext(), R.style.qs_theme); int index = mStatusBarWindow.indexOfChild(mBrightnessMirror); mStatusBarWindow.removeView(mBrightnessMirror); - mBrightnessMirror = LayoutInflater.from(mBrightnessMirror.getContext()).inflate( + mBrightnessMirror = LayoutInflater.from(qsThemeContext).inflate( R.layout.brightness_mirror, mStatusBarWindow, false); mStatusBarWindow.addView(mBrightnessMirror, index); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index 53dfb244c776..040d7ec32ecc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -16,119 +16,69 @@ package com.android.systemui.statusbar.policy; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.database.ContentObserver; +import android.os.SystemClock; import android.os.Handler; import android.os.Looper; -import android.os.SystemClock; -import android.provider.Settings; -import android.support.v4.util.ArraySet; import android.util.ArrayMap; +import android.provider.Settings; import android.util.Log; -import android.util.Pools; -import android.view.View; -import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; 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.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.StatusBar; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collection; +import java.util.Iterator; +import java.util.stream.Stream; import java.util.HashMap; import java.util.HashSet; -import java.util.Stack; /** * A manager which handles heads up notifications which is a special mode where * they simply peek from the top of the screen. */ -public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener, - VisualStabilityManager.Callback { +public class HeadsUpManager { private static final String TAG = "HeadsUpManager"; private static final boolean DEBUG = false; private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms"; - private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag; - private final int mHeadsUpNotificationDecay; - private final int mMinimumDisplayTime; + protected final Clock mClock = new Clock(); + protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>(); + protected final Handler mHandler = new Handler(Looper.getMainLooper()); - private final int mTouchAcceptanceDelay; - private final ArrayMap<String, Long> mSnoozedPackages; - private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>(); - private final int mDefaultSnoozeLengthMs; - private final Handler mHandler = new Handler(Looper.getMainLooper()); - private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() { + protected final Context mContext; - private Stack<HeadsUpEntry> mPoolObjects = new Stack<>(); + protected int mHeadsUpNotificationDecay; + protected int mMinimumDisplayTime; + protected int mTouchAcceptanceDelay; + protected int mSnoozeLengthMs; + protected boolean mHasPinnedNotification; + protected int mUser; - @Override - public HeadsUpEntry acquire() { - if (!mPoolObjects.isEmpty()) { - return mPoolObjects.pop(); - } - return new HeadsUpEntry(); - } + private final HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>(); + private final ArrayMap<String, Long> mSnoozedPackages; - @Override - public boolean release(HeadsUpEntry instance) { - instance.reset(); - mPoolObjects.push(instance); - return true; - } - }; - - private final View mStatusBarWindowView; - private final int mStatusBarHeight; - private final Context mContext; - private final NotificationGroupManager mGroupManager; - private StatusBar mBar; - private int mSnoozeLengthMs; - private ContentObserver mSettingsObserver; - private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>(); - private HashSet<String> mSwipedOutKeys = new HashSet<>(); - private int mUser; - private Clock mClock; - private boolean mReleaseOnExpandFinish; - private boolean mTrackingHeadsUp; - private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>(); - private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed - = new ArraySet<>(); - private boolean mIsExpanded; - private boolean mHasPinnedNotification; - private int[] mTmpTwoArray = new int[2]; - private boolean mHeadsUpGoingAway; - private boolean mWaitingOnCollapseWhenGoingAway; - private boolean mIsObserving; - private boolean mRemoteInputActive; - private float mExpandedHeight; - private VisualStabilityManager mVisualStabilityManager; - private int mStatusBarState; - - public HeadsUpManager(final Context context, View statusBarWindowView, - NotificationGroupManager groupManager) { + public HeadsUpManager(@NonNull final Context context) { mContext = context; - Resources resources = mContext.getResources(); - mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay); - mSnoozedPackages = new ArrayMap<>(); - mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms); - mSnoozeLengthMs = mDefaultSnoozeLengthMs; + Resources resources = context.getResources(); mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time); mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay); - mClock = new Clock(); + mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay); + mSnoozedPackages = new ArrayMap<>(); + int defaultSnoozeLengthMs = + resources.getInteger(R.integer.heads_up_default_snooze_length_ms); mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(), - SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs); - mSettingsObserver = new ContentObserver(mHandler) { + SETTING_HEADS_UP_SNOOZE_LENGTH_MS, defaultSnoozeLengthMs); + ContentObserver settingsObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { final int packageSnoozeLengthMs = Settings.Global.getInt( @@ -141,48 +91,27 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL }; context.getContentResolver().registerContentObserver( Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false, - mSettingsObserver); - mStatusBarWindowView = statusBarWindowView; - mGroupManager = groupManager; - mStatusBarHeight = resources.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height); - } - - private void updateTouchableRegionListener() { - boolean shouldObserve = mHasPinnedNotification || mHeadsUpGoingAway - || mWaitingOnCollapseWhenGoingAway; - if (shouldObserve == mIsObserving) { - return; - } - if (shouldObserve) { - mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this); - mStatusBarWindowView.requestLayout(); - } else { - mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); - } - mIsObserving = shouldObserve; + settingsObserver); } - public void setBar(StatusBar bar) { - mBar = bar; - } - - public void addListener(OnHeadsUpChangedListener listener) { + /** + * Adds an OnHeadUpChangedListener to observe events. + */ + public void addListener(@NonNull OnHeadsUpChangedListener listener) { mListeners.add(listener); } - public void removeListener(OnHeadsUpChangedListener listener) { + /** + * Removes the OnHeadUpChangedListener from the observer list. + */ + public void removeListener(@NonNull OnHeadsUpChangedListener listener) { mListeners.remove(listener); } - public StatusBar getBar() { - return mBar; - } - /** * Called when posting a new notification to the heads up. */ - public void showNotification(NotificationData.Entry headsUp) { + public void showNotification(@NonNull NotificationData.Entry headsUp) { if (DEBUG) Log.v(TAG, "showNotification"); addHeadsUpEntry(headsUp); updateNotification(headsUp, true); @@ -192,7 +121,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL /** * Called when updating or posting a notification to the heads up. */ - public void updateNotification(NotificationData.Entry headsUp, boolean alert) { + public void updateNotification(@NonNull NotificationData.Entry headsUp, boolean alert) { if (DEBUG) Log.v(TAG, "updateNotification"); headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); @@ -204,14 +133,13 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL // with the groupmanager return; } - headsUpEntry.updateEntry(); + headsUpEntry.updateEntry(true /* updatePostTime */); setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp)); } } - private void addHeadsUpEntry(NotificationData.Entry entry) { - HeadsUpEntry headsUpEntry = mEntryPool.acquire(); - + private void addHeadsUpEntry(@NonNull NotificationData.Entry entry) { + HeadsUpEntry headsUpEntry = createHeadsUpEntry(); // This will also add the entry to the sortedList headsUpEntry.setEntry(entry); mHeadsUpEntries.put(entry.key, headsUpEntry); @@ -223,16 +151,17 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); } - private boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) { - return mStatusBarState != StatusBarState.KEYGUARD - && !mIsExpanded || hasFullScreenIntent(entry); + protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationData.Entry entry) { + return hasFullScreenIntent(entry); } - private boolean hasFullScreenIntent(NotificationData.Entry entry) { + protected boolean hasFullScreenIntent(@NonNull NotificationData.Entry entry) { return entry.notification.getNotification().fullScreenIntent != null; } - private void setEntryPinned(HeadsUpEntry headsUpEntry, boolean isPinned) { + protected void setEntryPinned( + @NonNull HeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned) { + if (DEBUG) Log.v(TAG, "setEntryPinned: " + isPinned); ExpandableNotificationRow row = headsUpEntry.entry.row; if (row.isPinned() != isPinned) { row.setPinned(isPinned); @@ -247,33 +176,35 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } } - private void removeHeadsUpEntry(NotificationData.Entry entry) { + protected void removeHeadsUpEntry(@NonNull NotificationData.Entry entry) { HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key); + onHeadsUpEntryRemoved(remove); + } + + protected void onHeadsUpEntryRemoved(@NonNull HeadsUpEntry remove) { + NotificationData.Entry entry = remove.entry; entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); entry.row.setHeadsUp(false); setEntryPinned(remove, false /* isPinned */); for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, false); } - mEntryPool.release(remove); + releaseHeadsUpEntry(remove); } - public void removeAllHeadsUpEntries() { - for (String key : mHeadsUpEntries.keySet()) { - removeHeadsUpEntry(mHeadsUpEntries.get(key).entry); - } - } - - private void updatePinnedMode() { + protected void updatePinnedMode() { boolean hasPinnedNotification = hasPinnedNotificationInternal(); if (hasPinnedNotification == mHasPinnedNotification) { return; } + if (DEBUG) { + Log.v(TAG, "Pinned mode changed: " + mHasPinnedNotification + " -> " + + hasPinnedNotification); + } mHasPinnedNotification = hasPinnedNotification; if (mHasPinnedNotification) { MetricsLogger.count(mContext, "note_peek", 1); } - updateTouchableRegionListener(); for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpPinnedModeChanged(hasPinnedNotification); } @@ -285,47 +216,36 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL * @return true if the notification was removed and false if it still needs to be kept around * for a bit since it wasn't shown long enough */ - public boolean removeNotification(String key, boolean ignoreEarliestRemovalTime) { - if (DEBUG) Log.v(TAG, "remove"); - if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) { - releaseImmediately(key); - return true; - } else { - getHeadsUpEntry(key).removeAsSoonAsPossible(); - return false; - } + public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) { + if (DEBUG) Log.v(TAG, "removeNotification"); + releaseImmediately(key); + return true; } - private boolean wasShownLongEnough(String key) { - HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); - HeadsUpEntry topEntry = getTopEntry(); - if (mSwipedOutKeys.contains(key)) { - // We always instantly dismiss views being manually swiped out. - mSwipedOutKeys.remove(key); - return true; - } - if (headsUpEntry != topEntry) { - return true; - } - return headsUpEntry.wasShownLongEnough(); - } - - public boolean isHeadsUp(String key) { + /** + * Returns if the given notification is in the Heads Up Notification list or not. + */ + public boolean isHeadsUp(@NonNull String key) { return mHeadsUpEntries.containsKey(key); } /** - * Push any current Heads Up notification down into the shade. + * Pushes any current Heads Up notification down into the shade. */ public void releaseAllImmediately() { if (DEBUG) Log.v(TAG, "releaseAllImmediately"); - ArrayList<String> keys = new ArrayList<>(mHeadsUpEntries.keySet()); - for (String key : keys) { - releaseImmediately(key); + Iterator<HeadsUpEntry> iterator = mHeadsUpEntries.values().iterator(); + while (iterator.hasNext()) { + HeadsUpEntry entry = iterator.next(); + iterator.remove(); + onHeadsUpEntryRemoved(entry); } } - public void releaseImmediately(String key) { + /** + * Pushes the given Heads Up notification down into the shade. + */ + public void releaseImmediately(@NonNull String key) { HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); if (headsUpEntry == null) { return; @@ -334,11 +254,14 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL removeHeadsUpEntry(shadeEntry); } - public boolean isSnoozed(String packageName) { + /** + * Returns if the given notification is snoozed or not. + */ + public boolean isSnoozed(@NonNull String packageName) { final String key = snoozeKey(packageName, mUser); Long snoozedUntil = mSnoozedPackages.get(key); if (snoozedUntil != null) { - if (snoozedUntil > SystemClock.elapsedRealtime()) { + if (snoozedUntil > mClock.currentTimeMillis()) { if (DEBUG) Log.v(TAG, key + " snoozed"); return true; } @@ -347,39 +270,71 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL return false; } + /** + * Snoozes all current Heads Up Notifications. + */ public void snooze() { for (String key : mHeadsUpEntries.keySet()) { HeadsUpEntry entry = mHeadsUpEntries.get(key); String packageName = entry.entry.notification.getPackageName(); mSnoozedPackages.put(snoozeKey(packageName, mUser), - SystemClock.elapsedRealtime() + mSnoozeLengthMs); + mClock.currentTimeMillis() + mSnoozeLengthMs); } - mReleaseOnExpandFinish = true; } - private static String snoozeKey(String packageName, int user) { + @NonNull + private static String snoozeKey(@NonNull String packageName, int user) { return user + "," + packageName; } - private HeadsUpEntry getHeadsUpEntry(String key) { + @Nullable + protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) { return mHeadsUpEntries.get(key); } - public NotificationData.Entry getEntry(String key) { - return mHeadsUpEntries.get(key).entry; + /** + * Returns the entry of given Heads Up Notification. + * + * @param key Key of heads up notification + */ + @Nullable + public NotificationData.Entry getEntry(@NonNull String key) { + HeadsUpEntry entry = mHeadsUpEntries.get(key); + return entry != null ? entry.entry : null; + } + + /** + * Returns the stream of all current Heads Up Notifications. + */ + @NonNull + public Stream<NotificationData.Entry> getAllEntries() { + return mHeadsUpEntries.values().stream().map(headsUpEntry -> headsUpEntry.entry); + } + + /** + * Returns the top Heads Up Notification, which appeares to show at first. + */ + @Nullable + public NotificationData.Entry getTopEntry() { + HeadsUpEntry topEntry = getTopHeadsUpEntry(); + return (topEntry != null) ? topEntry.entry : null; } - public Collection<HeadsUpEntry> getAllEntries() { - return mHeadsUpEntries.values(); + /** + * Returns if any heads up notification is available or not. + */ + public boolean hasHeadsUpNotifications() { + return !mHeadsUpEntries.isEmpty(); } - public HeadsUpEntry getTopEntry() { + @Nullable + protected HeadsUpEntry getTopHeadsUpEntry() { if (mHeadsUpEntries.isEmpty()) { return null; } HeadsUpEntry topEntry = null; for (HeadsUpEntry entry: mHeadsUpEntries.values()) { - if (topEntry == null || entry.compareTo(topEntry) == -1) { + if (topEntry == null || entry.compareTo(topEntry) < 0) { topEntry = entry; } } @@ -387,56 +342,22 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } /** - * Decides whether a click is invalid for a notification, i.e it has not been shown long enough - * that a user might have consciously clicked on it. - * - * @param key the key of the touched notification - * @return whether the touch is invalid and should be discarded + * Sets the current user. */ - public boolean shouldSwallowClick(String key) { - HeadsUpEntry entry = mHeadsUpEntries.get(key); - if (entry != null && mClock.currentTimeMillis() < entry.postTime) { - return true; - } - return false; - } - - public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { - if (mIsExpanded || mBar.isBouncerShowing()) { - // The touchable region is always the full area when expanded - return; - } - if (mHasPinnedNotification) { - ExpandableNotificationRow topEntry = getTopEntry().entry.row; - if (topEntry.isChildInGroup()) { - final ExpandableNotificationRow groupSummary - = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification()); - if (groupSummary != null) { - topEntry = groupSummary; - } - } - topEntry.getLocationOnScreen(mTmpTwoArray); - int minX = mTmpTwoArray[0]; - int maxX = mTmpTwoArray[0] + topEntry.getWidth(); - int maxY = topEntry.getIntrinsicHeight(); - - info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - info.touchableRegion.set(minX, 0, maxX, maxY); - } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) { - info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); - } - } - public void setUser(int user) { mUser = user; } - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { pw.println("HeadsUpManager state:"); + dumpInternal(fd, pw, args); + } + + protected void dumpInternal( + @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay); pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs); - pw.print(" now="); pw.println(SystemClock.elapsedRealtime()); + pw.print(" now="); pw.println(mClock.currentTimeMillis()); pw.print(" mUser="); pw.println(mUser); for (HeadsUpEntry entry: mHeadsUpEntries.values()) { pw.print(" HeadsUpEntry="); pw.println(entry.entry); @@ -449,6 +370,9 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } } + /** + * Returns if there are any pinned Heads Up Notifications or not. + */ public boolean hasPinnedHeadsUp() { return mHasPinnedNotification; } @@ -464,14 +388,8 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } /** - * Notifies that a notification was swiped out and will be removed. - * - * @param key the notification key + * Unpins all pinned Heads Up Notifications. */ - public void addSwipedOutNotification(String key) { - mSwipedOutKeys.add(key); - } - public void unpinAll() { for (String key : mHeadsUpEntries.keySet()) { HeadsUpEntry entry = mHeadsUpEntries.get(key); @@ -481,60 +399,13 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } } - public void onExpandingFinished() { - if (mReleaseOnExpandFinish) { - releaseAllImmediately(); - mReleaseOnExpandFinish = false; - } else { - for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) { - if (isHeadsUp(entry.key)) { - // Maybe the heads-up was removed already - removeHeadsUpEntry(entry); - } - } - } - mEntriesToRemoveAfterExpand.clear(); - } - - public void setTrackingHeadsUp(boolean trackingHeadsUp) { - mTrackingHeadsUp = trackingHeadsUp; - } - - public boolean isTrackingHeadsUp() { - return mTrackingHeadsUp; - } - - public void setIsExpanded(boolean isExpanded) { - if (isExpanded != mIsExpanded) { - mIsExpanded = isExpanded; - if (isExpanded) { - // make sure our state is sane - mWaitingOnCollapseWhenGoingAway = false; - mHeadsUpGoingAway = false; - updateTouchableRegionListener(); - } - } - } - /** - * @return the height of the top heads up notification when pinned. This is different from the - * intrinsic height, which also includes whether the notification is system expanded and - * is mainly used when dragging down from a heads up notification. + * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as + * well. */ - public int getTopHeadsUpPinnedHeight() { - HeadsUpEntry topEntry = getTopEntry(); - if (topEntry == null || topEntry.entry == null) { - return 0; - } - ExpandableNotificationRow row = topEntry.entry.row; - if (row.isChildInGroup()) { - final ExpandableNotificationRow groupSummary - = mGroupManager.getGroupSummary(row.getStatusBarNotification()); - if (groupSummary != null) { - row = groupSummary; - } - } - return row.getPinnedHeadsUpHeight(); + public boolean isTrackingHeadsUp() { + // Might be implemented in subclass. + return false; } /** @@ -543,7 +414,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL * @return -1 if the first argument should be ranked higher than the second, 1 if the second * one should be ranked higher and 0 if they are equal. */ - public int compare(NotificationData.Entry a, NotificationData.Entry b) { + public int compare(@NonNull NotificationData.Entry a, @NonNull NotificationData.Entry b) { HeadsUpEntry aEntry = getHeadsUpEntry(a.key); HeadsUpEntry bEntry = getHeadsUpEntry(b.key); if (aEntry == null || bEntry == null) { @@ -553,147 +424,62 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } /** - * Set that we are exiting the headsUp pinned mode, but some notifications might still be - * animating out. This is used to keep the touchable regions in a sane state. - */ - public void setHeadsUpGoingAway(boolean headsUpGoingAway) { - if (headsUpGoingAway != mHeadsUpGoingAway) { - mHeadsUpGoingAway = headsUpGoingAway; - if (!headsUpGoingAway) { - waitForStatusBarLayout(); - } - updateTouchableRegionListener(); - } - } - - /** - * We need to wait on the whole panel to collapse, before we can remove the touchable region - * listener. - */ - private void waitForStatusBarLayout() { - mWaitingOnCollapseWhenGoingAway = true; - mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, - int oldTop, int oldRight, int oldBottom) { - if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) { - mStatusBarWindowView.removeOnLayoutChangeListener(this); - mWaitingOnCollapseWhenGoingAway = false; - updateTouchableRegionListener(); - } - } - }); - } - - public static void setIsClickedNotification(View child, boolean clicked) { - child.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null); - } - - public static boolean isClickedHeadsUpNotification(View child) { - Boolean clicked = (Boolean) child.getTag(TAG_CLICKED_NOTIFICATION); - return clicked != null && clicked; - } - - public void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive) { - HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key); - if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) { - headsUpEntry.remoteInputActive = remoteInputActive; - if (remoteInputActive) { - headsUpEntry.removeAutoRemovalCallbacks(); - } else { - headsUpEntry.updateEntry(false /* updatePostTime */); - } - } - } - - /** * Set an entry to be expanded and therefore stick in the heads up area if it's pinned * until it's collapsed again. */ - public void setExpanded(NotificationData.Entry entry, boolean expanded) { - HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key); - if (headsUpEntry != null && headsUpEntry.expanded != expanded && entry.row.isPinned()) { - headsUpEntry.expanded = expanded; - if (expanded) { - headsUpEntry.removeAutoRemovalCallbacks(); - } else { - headsUpEntry.updateEntry(false /* updatePostTime */); - } + public void setExpanded(@NonNull NotificationData.Entry entry, boolean expanded) { + HeadsUpManager.HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key); + if (headsUpEntry != null && entry.row.isPinned()) { + headsUpEntry.expanded(expanded); } } - @Override - public void onReorderingAllowed() { - mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false); - for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) { - if (isHeadsUp(entry.key)) { - // Maybe the heads-up was removed already - removeHeadsUpEntry(entry); - } - } - mEntriesToRemoveWhenReorderingAllowed.clear(); - mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true); + @NonNull + protected HeadsUpEntry createHeadsUpEntry() { + return new HeadsUpEntry(); } - public void setVisualStabilityManager(VisualStabilityManager visualStabilityManager) { - mVisualStabilityManager = visualStabilityManager; - } - - public void setStatusBarState(int statusBarState) { - mStatusBarState = statusBarState; + protected void releaseHeadsUpEntry(@NonNull HeadsUpEntry entry) { + entry.reset(); } /** * This represents a notification and how long it is in a heads up mode. It also manages its * lifecycle automatically when created. */ - public class HeadsUpEntry implements Comparable<HeadsUpEntry> { - public NotificationData.Entry entry; + protected class HeadsUpEntry implements Comparable<HeadsUpEntry> { + @Nullable public NotificationData.Entry entry; public long postTime; - public long earliestRemovaltime; - private Runnable mRemoveHeadsUpRunnable; public boolean remoteInputActive; + public long earliestRemovaltime; public boolean expanded; - public void setEntry(final NotificationData.Entry entry) { + @Nullable private Runnable mRemoveHeadsUpRunnable; + + public void setEntry(@Nullable final NotificationData.Entry entry) { + setEntry(entry, null); + } + + public void setEntry(@Nullable final NotificationData.Entry entry, + @Nullable Runnable removeHeadsUpRunnable) { this.entry = entry; + this.mRemoveHeadsUpRunnable = removeHeadsUpRunnable; // The actual post time will be just after the heads-up really slided in postTime = mClock.currentTimeMillis() + mTouchAcceptanceDelay; - mRemoveHeadsUpRunnable = new Runnable() { - @Override - public void run() { - if (!mVisualStabilityManager.isReorderingAllowed()) { - mEntriesToRemoveWhenReorderingAllowed.add(entry); - mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManager.this); - } else if (!mTrackingHeadsUp) { - removeHeadsUpEntry(entry); - } else { - mEntriesToRemoveAfterExpand.add(entry); - } - } - }; - updateEntry(); - } - - public void updateEntry() { - updateEntry(true); + updateEntry(true /* updatePostTime */); } public void updateEntry(boolean updatePostTime) { + if (DEBUG) Log.v(TAG, "updateEntry"); + long currentTime = mClock.currentTimeMillis(); earliestRemovaltime = currentTime + mMinimumDisplayTime; if (updatePostTime) { postTime = Math.max(postTime, currentTime); } removeAutoRemovalCallbacks(); - if (mEntriesToRemoveAfterExpand.contains(entry)) { - mEntriesToRemoveAfterExpand.remove(entry); - } - if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) { - mEntriesToRemoveWhenReorderingAllowed.remove(entry); - } + if (!isSticky()) { long finishTime = postTime + mHeadsUpNotificationDecay; long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime); @@ -707,7 +493,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } @Override - public int compareTo(HeadsUpEntry o) { + public int compareTo(@NonNull HeadsUpEntry o) { boolean isPinned = entry.row.isPinned(); boolean otherPinned = o.entry.row.isPinned(); if (isPinned && !otherPinned) { @@ -734,26 +520,29 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL : -1; } - public void removeAutoRemovalCallbacks() { - mHandler.removeCallbacks(mRemoveHeadsUpRunnable); - } - - public boolean wasShownLongEnough() { - return earliestRemovaltime < mClock.currentTimeMillis(); - } - - public void removeAsSoonAsPossible() { - removeAutoRemovalCallbacks(); - mHandler.postDelayed(mRemoveHeadsUpRunnable, - earliestRemovaltime - mClock.currentTimeMillis()); + public void expanded(boolean expanded) { + this.expanded = expanded; } public void reset() { - removeAutoRemovalCallbacks(); entry = null; - mRemoveHeadsUpRunnable = null; expanded = false; remoteInputActive = false; + removeAutoRemovalCallbacks(); + mRemoveHeadsUpRunnable = null; + } + + public void removeAutoRemovalCallbacks() { + if (mRemoveHeadsUpRunnable != null) + mHandler.removeCallbacks(mRemoveHeadsUpRunnable); + } + + public void removeAsSoonAsPossible() { + if (mRemoveHeadsUpRunnable != null) { + removeAutoRemovalCallbacks(); + mHandler.postDelayed(mRemoveHeadsUpRunnable, + earliestRemovaltime - mClock.currentTimeMillis()); + } } } @@ -762,5 +551,4 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL return SystemClock.elapsedRealtime(); } } - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java new file mode 100644 index 000000000000..1e3c123cfbc6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java @@ -0,0 +1,47 @@ +/* + * 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.policy; + +import android.view.View; + +import com.android.systemui.R; + +/** + * A class of utility static methods for heads up notifications. + */ +public final class HeadsUpUtil { + private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag; + + /** + * Set the given view as clicked or not-clicked. + * @param view The view to be set the flag to. + * @param clicked True to set as clicked. False to not-clicked. + */ + public static void setIsClickedHeadsUpNotification(View view, boolean clicked) { + view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null); + } + + /** + * Check if the given view has the flag of "clicked notification" + * @param view The view to be checked. + * @return True if the view has clicked. False othrewise. + */ + public static boolean isClickedHeadsUpNotification(View view) { + Boolean clicked = (Boolean) view.getTag(TAG_CLICKED_NOTIFICATION); + return clicked != null && clicked; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java index 6457209a407b..830b50e35490 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java @@ -26,7 +26,9 @@ public interface HotspotController extends CallbackController<Callback>, Dumpabl void setHotspotEnabled(boolean enabled); boolean isHotspotSupported(); - public interface Callback { - void onHotspotChanged(boolean enabled); + int getNumConnectedDevices(); + + interface Callback { + void onHotspotChanged(boolean enabled, int numDevices); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java index 1ebb986e4488..8792b4f3cc4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java @@ -23,31 +23,35 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.wifi.WifiManager; -import android.os.Handler; import android.os.UserManager; import android.util.Log; +import com.android.systemui.Dependency; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -public class HotspotControllerImpl implements HotspotController { +public class HotspotControllerImpl implements HotspotController, WifiManager.SoftApCallback { private static final String TAG = "HotspotController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); - private final Receiver mReceiver = new Receiver(); + private final ArrayList<Callback> mCallbacks = new ArrayList<>(); + private final WifiStateReceiver mWifiStateReceiver = new WifiStateReceiver(); private final ConnectivityManager mConnectivityManager; + private final WifiManager mWifiManager; private final Context mContext; private int mHotspotState; + private int mNumConnectedDevices; private boolean mWaitingForCallback; public HotspotControllerImpl(Context context) { mContext = context; - mConnectivityManager = (ConnectivityManager) context.getSystemService( - Context.CONNECTIVITY_SERVICE); + mConnectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); } @Override @@ -84,7 +88,8 @@ public class HotspotControllerImpl implements HotspotController { if (callback == null || mCallbacks.contains(callback)) return; if (DEBUG) Log.d(TAG, "addCallback " + callback); mCallbacks.add(callback); - mReceiver.setListening(!mCallbacks.isEmpty()); + + updateWifiStateListeners(!mCallbacks.isEmpty()); } } @@ -94,7 +99,26 @@ public class HotspotControllerImpl implements HotspotController { if (DEBUG) Log.d(TAG, "removeCallback " + callback); synchronized (mCallbacks) { mCallbacks.remove(callback); - mReceiver.setListening(!mCallbacks.isEmpty()); + + updateWifiStateListeners(!mCallbacks.isEmpty()); + } + } + + /** + * Updates the wifi state receiver to either start or stop listening to get updates to the + * hotspot status. Additionally starts listening to wifi manager state to track the number of + * connected devices. + * + * @param shouldListen whether we should start listening to various wifi statuses + */ + private void updateWifiStateListeners(boolean shouldListen) { + mWifiStateReceiver.setListening(shouldListen); + if (shouldListen) { + mWifiManager.registerSoftApCallback( + this, + Dependency.get(Dependency.MAIN_HANDLER)); + } else { + mWifiManager.unregisterSoftApCallback(this); } } @@ -116,20 +140,55 @@ public class HotspotControllerImpl implements HotspotController { if (DEBUG) Log.d(TAG, "Starting tethering"); mConnectivityManager.startTethering( ConnectivityManager.TETHERING_WIFI, false, callback); - fireCallback(isHotspotEnabled()); + fireHotspotChangedCallback(isHotspotEnabled()); } else { mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI); } } - private void fireCallback(boolean isEnabled) { + @Override + public int getNumConnectedDevices() { + return mNumConnectedDevices; + } + + /** + * Sends a hotspot changed callback with the new enabled status. Wraps + * {@link #fireHotspotChangedCallback(boolean, int)} and assumes that the number of devices has + * not changed. + * + * @param enabled whether the hotspot is enabled + */ + private void fireHotspotChangedCallback(boolean enabled) { + fireHotspotChangedCallback(enabled, mNumConnectedDevices); + } + + /** + * Sends a hotspot changed callback with the new enabled status & the number of devices + * connected to the hotspot. Be careful when calling over multiple threads, especially if one of + * them is the main thread (as it can be blocked). + * + * @param enabled whether the hotspot is enabled + * @param numConnectedDevices number of devices connected to the hotspot + */ + private void fireHotspotChangedCallback(boolean enabled, int numConnectedDevices) { synchronized (mCallbacks) { for (Callback callback : mCallbacks) { - callback.onHotspotChanged(isEnabled); + callback.onHotspotChanged(enabled, numConnectedDevices); } } } + @Override + public void onStateChanged(int state, int failureReason) { + // Do nothing - we don't care about changing anything here. + } + + @Override + public void onNumClientsChanged(int numConnectedDevices) { + mNumConnectedDevices = numConnectedDevices; + fireHotspotChangedCallback(isHotspotEnabled(), numConnectedDevices); + } + private final class OnStartTetheringCallback extends ConnectivityManager.OnStartTetheringCallback { @Override @@ -143,12 +202,15 @@ public class HotspotControllerImpl implements HotspotController { public void onTetheringFailed() { if (DEBUG) Log.d(TAG, "onTetheringFailed"); mWaitingForCallback = false; - fireCallback(isHotspotEnabled()); + fireHotspotChangedCallback(isHotspotEnabled()); // TODO: Show error. } } - private final class Receiver extends BroadcastReceiver { + /** + * Class to listen in on wifi state and update the hotspot state + */ + private final class WifiStateReceiver extends BroadcastReceiver { private boolean mRegistered; public void setListening(boolean listening) { @@ -170,8 +232,17 @@ public class HotspotControllerImpl implements HotspotController { int state = intent.getIntExtra( WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_FAILED); if (DEBUG) Log.d(TAG, "onReceive " + state); + + // Update internal hotspot state for tracking before using any enabled/callback methods. mHotspotState = state; - fireCallback(mHotspotState == WifiManager.WIFI_AP_STATE_ENABLED); + + if (!isHotspotEnabled()) { + // Reset num devices if the hotspot is no longer enabled so we don't get ghost + // counters. + mNumConnectedDevices = 0; + } + + fireHotspotChangedCallback(isHotspotEnabled()); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java index 21a96e2ea06a..cce9d1cd6d95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java @@ -39,7 +39,7 @@ public class KeyButtonDrawable extends LayerDrawable { } } - private KeyButtonDrawable(Drawable[] drawables) { + protected KeyButtonDrawable(Drawable[] drawables) { super(drawables); for (int i = 0; i < drawables.length; i++) { setLayerGravity(i, Gravity.CENTER); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java index cc7943b85bc1..a2bec982ed58 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java @@ -26,9 +26,11 @@ import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; +import android.os.Handler; import android.view.DisplayListCanvas; import android.view.RenderNodeAnimator; import android.view.View; +import android.view.ViewConfiguration; import android.view.animation.Interpolator; import com.android.systemui.Interpolators; @@ -56,14 +58,17 @@ public class KeyButtonRipple extends Drawable { private float mGlowAlpha = 0f; private float mGlowScale = 1f; private boolean mPressed; + private boolean mVisible; private boolean mDrawingHardwareGlow; private int mMaxWidth; private boolean mLastDark; private boolean mDark; + private boolean mDelayTouchFeedback; private final Interpolator mInterpolator = new LogInterpolator(); private boolean mSupportHardware; private final View mTargetView; + private final Handler mHandler = new Handler(); private final HashSet<Animator> mRunningAnimations = new HashSet<>(); private final ArrayList<Animator> mTmpArray = new ArrayList<>(); @@ -77,6 +82,10 @@ public class KeyButtonRipple extends Drawable { mDark = darkIntensity >= 0.5f; } + public void setDelayTouchFeedback(boolean delay) { + mDelayTouchFeedback = delay; + } + private Paint getRipplePaint() { if (mRipplePaint == null) { mRipplePaint = new Paint(); @@ -211,7 +220,16 @@ public class KeyButtonRipple extends Drawable { } } + /** + * Abort the ripple while it is delayed and before shown used only when setShouldDelayStartTouch + * is enabled. + */ + public void abortDelayedRipple() { + mHandler.removeCallbacksAndMessages(null); + } + private void cancelAnimations() { + mVisible = false; mTmpArray.addAll(mRunningAnimations); int size = mTmpArray.size(); for (int i = 0; i < size; i++) { @@ -220,11 +238,21 @@ public class KeyButtonRipple extends Drawable { } mTmpArray.clear(); mRunningAnimations.clear(); + mHandler.removeCallbacksAndMessages(null); } private void setPressedSoftware(boolean pressed) { if (pressed) { - enterSoftware(); + if (mDelayTouchFeedback) { + if (mRunningAnimations.isEmpty()) { + mHandler.removeCallbacksAndMessages(null); + mHandler.postDelayed(this::enterSoftware, ViewConfiguration.getTapTimeout()); + } else if (mVisible) { + enterSoftware(); + } + } else { + enterSoftware(); + } } else { exitSoftware(); } @@ -232,6 +260,7 @@ public class KeyButtonRipple extends Drawable { private void enterSoftware() { cancelAnimations(); + mVisible = true; mGlowAlpha = getMaxGlowAlpha(); ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale", 0f, GLOW_MAX_SCALE_FACTOR); @@ -240,6 +269,12 @@ public class KeyButtonRipple extends Drawable { scaleAnimator.addListener(mAnimatorListener); scaleAnimator.start(); mRunningAnimations.add(scaleAnimator); + + // With the delay, it could eventually animate the enter animation with no pressed state, + // then immediately show the exit animation. If this is skipped there will be no ripple. + if (mDelayTouchFeedback && !mPressed) { + exitSoftware(); + } } private void exitSoftware() { @@ -253,7 +288,16 @@ public class KeyButtonRipple extends Drawable { private void setPressedHardware(boolean pressed) { if (pressed) { - enterHardware(); + if (mDelayTouchFeedback) { + if (mRunningAnimations.isEmpty()) { + mHandler.removeCallbacksAndMessages(null); + mHandler.postDelayed(this::enterHardware, ViewConfiguration.getTapTimeout()); + } else if (mVisible) { + enterHardware(); + } + } else { + enterHardware(); + } } else { exitHardware(); } @@ -302,6 +346,7 @@ public class KeyButtonRipple extends Drawable { private void enterHardware() { cancelAnimations(); + mVisible = true; mDrawingHardwareGlow = true; setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2)); final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(), @@ -343,6 +388,12 @@ public class KeyButtonRipple extends Drawable { mRunningAnimations.add(endAnim); invalidateSelf(); + + // With the delay, it could eventually animate the enter animation with no pressed state, + // then immediately show the exit animation. If this is skipped there will be no ripple. + if (mDelayTouchFeedback && !mPressed) { + exitHardware(); + } } private void exitHardware() { @@ -366,6 +417,7 @@ public class KeyButtonRipple extends Drawable { public void onAnimationEnd(Animator animation) { mRunningAnimations.remove(animation); if (mRunningAnimations.isEmpty() && !mPressed) { + mVisible = false; mDrawingHardwareGlow = false; invalidateSelf(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index 05017718d42b..98bebec8511e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -27,8 +27,12 @@ import android.media.AudioManager; import android.metrics.LogMaker; import android.os.AsyncTask; import android.os.Bundle; +import android.os.RemoteException; import android.os.SystemClock; +import android.os.VibrationEffect; +import android.os.Vibrator; import android.util.AttributeSet; +import android.util.Log; import android.util.TypedValue; import android.view.HapticFeedbackConstants; import android.view.InputDevice; @@ -45,25 +49,32 @@ import android.widget.ImageView; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Dependency; +import com.android.systemui.OverviewProxyService; import com.android.systemui.R; import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface; +import com.android.systemui.shared.system.ActivityManagerWrapper; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK; public class KeyButtonView extends ImageView implements ButtonInterface { + private static final String TAG = KeyButtonView.class.getSimpleName(); private final boolean mPlaySounds; private int mContentDescriptionRes; private long mDownTime; private int mCode; private int mTouchSlop; + private int mTouchDownX; + private int mTouchDownY; private boolean mSupportsLongpress = true; private AudioManager mAudioManager; private boolean mGestureAborted; private boolean mLongClicked; private OnClickListener mOnClickListener; private final KeyButtonRipple mRipple; + private final OverviewProxyService mOverviewProxyService; + private final Vibrator mVibrator; private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); private final Runnable mCheckLongPress = new Runnable() { @@ -110,6 +121,8 @@ public class KeyButtonView extends ImageView implements ButtonInterface { mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mRipple = new KeyButtonRipple(context, this); + mVibrator = mContext.getSystemService(Vibrator.class); + mOverviewProxyService = Dependency.get(OverviewProxyService.class); setBackground(mRipple); } @@ -189,6 +202,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { } public boolean onTouchEvent(MotionEvent ev) { + final boolean isProxyConnected = mOverviewProxyService.getProxy() != null; final int action = ev.getAction(); int x, y; if (action == MotionEvent.ACTION_DOWN) { @@ -203,23 +217,34 @@ public class KeyButtonView extends ImageView implements ButtonInterface { mDownTime = SystemClock.uptimeMillis(); mLongClicked = false; setPressed(true); + mTouchDownX = (int) ev.getX(); + mTouchDownY = (int) ev.getY(); if (mCode != 0) { sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime); } else { // Provide the same haptic feedback that the system offers for virtual keys. performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); } - playSoundEffect(SoundEffectConstants.CLICK); + if (isProxyConnected) { + // Provide small vibration for quick step or immediate down feedback + AsyncTask.execute(() -> + mVibrator.vibrate(VibrationEffect + .get(VibrationEffect.EFFECT_TICK, false))); + } else { + playSoundEffect(SoundEffectConstants.CLICK); + } removeCallbacks(mCheckLongPress); postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout()); break; case MotionEvent.ACTION_MOVE: x = (int)ev.getX(); y = (int)ev.getY(); - setPressed(x >= -mTouchSlop - && x < getWidth() + mTouchSlop - && y >= -mTouchSlop - && y < getHeight() + mTouchSlop); + boolean exceededTouchSlopX = Math.abs(x - mTouchDownX) > mTouchSlop; + boolean exceededTouchSlopY = Math.abs(y - mTouchDownY) > mTouchSlop; + if (exceededTouchSlopX || exceededTouchSlopY) { + setPressed(false); + removeCallbacks(mCheckLongPress); + } break; case MotionEvent.ACTION_CANCEL: setPressed(false); @@ -231,13 +256,21 @@ public class KeyButtonView extends ImageView implements ButtonInterface { case MotionEvent.ACTION_UP: final boolean doIt = isPressed() && !mLongClicked; setPressed(false); - // Always send a release ourselves because it doesn't seem to be sent elsewhere - // and it feels weird to sometimes get a release haptic and other times not. - if ((SystemClock.uptimeMillis() - mDownTime) > 150 && !mLongClicked) { + if (isProxyConnected) { + if (doIt) { + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + playSoundEffect(SoundEffectConstants.CLICK); + } + } else if ((SystemClock.uptimeMillis() - mDownTime) > 150 && !mLongClicked) { + // Always send a release ourselves because it doesn't seem to be sent elsewhere + // and it feels weird to sometimes get a release haptic and other times not. performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE); } if (mCode != 0) { if (doIt) { + // If there was a pending remote recents animation, then we need to + // cancel the animation now before we handle the button itself + ActivityManagerWrapper.getInstance().cancelRecentsAnimation(); sendEvent(KeyEvent.ACTION_UP, 0); sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); } else { @@ -284,6 +317,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { @Override public void abortCurrentGesture() { setPressed(false); + mRipple.abortDelayedRipple(); mGestureAborted = true; } @@ -301,6 +335,11 @@ public class KeyButtonView extends ImageView implements ButtonInterface { } @Override + public void setDelayTouchFeedback(boolean shouldDelay) { + mRipple.setDelayTouchFeedback(shouldDelay); + } + + @Override public void setVertical(boolean vertical) { //no op } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java index 874f0d9d5b5f..3febdfdfbe7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java @@ -16,11 +16,12 @@ package com.android.systemui.statusbar.policy; +import static com.android.settingslib.Utils.updateLocationEnabled; + import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.StatusBarManager; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -28,14 +29,12 @@ import android.location.LocationManager; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.support.annotation.VisibleForTesting; - -import com.android.systemui.R; import com.android.systemui.util.Utils; - import java.util.ArrayList; import java.util.List; @@ -99,31 +98,29 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio * @return true if attempt to change setting was successful. */ public boolean setLocationEnabled(boolean enabled) { + // QuickSettings always runs as the owner, so specifically set the settings + // for the current foreground user. int currentUserId = ActivityManager.getCurrentUser(); if (isUserLocationRestricted(currentUserId)) { return false; } - final ContentResolver cr = mContext.getContentResolver(); // When enabling location, a user consent dialog will pop up, and the // setting won't be fully enabled until the user accepts the agreement. - int mode = enabled - ? Settings.Secure.LOCATION_MODE_PREVIOUS : Settings.Secure.LOCATION_MODE_OFF; - // QuickSettings always runs as the owner, so specifically set the settings - // for the current foreground user. - return Settings.Secure - .putIntForUser(cr, Settings.Secure.LOCATION_MODE, mode, currentUserId); + updateLocationEnabled(mContext, enabled, currentUserId, + Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS); + return true; } /** - * Returns true if location isn't disabled in settings. + * Returns true if location is enabled in settings. */ public boolean isLocationEnabled() { - ContentResolver resolver = mContext.getContentResolver(); // QuickSettings always runs as the owner, so specifically retrieve the settings // for the current foreground user. - int mode = Settings.Secure.getIntForUser(resolver, Settings.Secure.LOCATION_MODE, - Settings.Secure.LOCATION_MODE_OFF, ActivityManager.getCurrentUser()); - return mode != Settings.Secure.LOCATION_MODE_OFF; + LocationManager locationManager = + (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); + return locationManager.isLocationEnabledForUser( + UserHandle.of(ActivityManager.getCurrentUser())); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index 8516278a8891..f0854edeaece 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -35,8 +35,8 @@ import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.cdma.EriInfo; +import com.android.settingslib.graph.SignalDrawable; import com.android.systemui.R; -import com.android.systemui.statusbar.phone.SignalDrawable; import com.android.systemui.statusbar.policy.NetworkController.IconState; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; import com.android.systemui.statusbar.policy.NetworkControllerImpl.Config; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index d45a5ec70782..baf0ebf5efaa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -152,7 +152,7 @@ public class NetworkControllerImpl extends BroadcastReceiver (WifiManager) context.getSystemService(Context.WIFI_SERVICE), SubscriptionManager.from(context), Config.readConfig(context), bgLooper, new CallbackHandler(), - new AccessPointControllerImpl(context, bgLooper), + new AccessPointControllerImpl(context), new DataUsageController(context), new SubscriptionDefaults(), deviceProvisionedController); @@ -385,13 +385,6 @@ public class NetworkControllerImpl extends BroadcastReceiver new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... args) { - // Disable tethering if enabling Wifi - final int wifiApState = mWifiManager.getWifiApState(); - if (enabled && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) || - (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) { - mWifiManager.setWifiApEnabled(null, false); - } - mWifiManager.setWifiEnabled(enabled); return null; } @@ -828,6 +821,10 @@ public class NetworkControllerImpl extends BroadcastReceiver } else { mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_NONE); } + String ssid = args.getString("ssid"); + if (ssid != null) { + mDemoWifiState.ssid = ssid; + } mDemoWifiState.enabled = show; mWifiSignalController.notifyListeners(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 37b0de400c9e..179c0d57aa50 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -27,7 +27,9 @@ import android.content.pm.ShortcutManager; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.os.SystemClock; import android.text.Editable; +import android.text.SpannedString; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.Log; @@ -53,11 +55,9 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.notification.NotificationViewWrapper; -import com.android.systemui.statusbar.stack.ScrollContainer; import com.android.systemui.statusbar.stack.StackStateAnimator; /** @@ -82,8 +82,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private NotificationData.Entry mEntry; - private ScrollContainer mScrollContainer; - private View mScrollContainerChild; private boolean mRemoved; private int mRevealCx; @@ -139,11 +137,13 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent, results); + RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT); mEditText.setEnabled(false); mSendButton.setVisibility(INVISIBLE); mProgressBar.setVisibility(VISIBLE); mEntry.remoteInputText = mEditText.getText(); + mEntry.lastRemoteInputSent = SystemClock.elapsedRealtime(); mController.addSpinning(mEntry.key, mToken); mController.removeRemoteInput(mEntry, mToken); mEditText.mShowImeOnInputConnection = false; @@ -169,6 +169,10 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } } + public CharSequence getText() { + return mEditText.getText(); + } + public static RemoteInputView inflate(Context context, ViewGroup root, NotificationData.Entry entry, RemoteInputController controller) { @@ -302,6 +306,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private void reset() { mResetting = true; + mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText()); mEditText.getText().clear(); mEditText.setEnabled(true); @@ -347,41 +352,16 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { - findScrollContainer(); - if (mScrollContainer != null) { - mScrollContainer.requestDisallowLongPress(); - mScrollContainer.requestDisallowDismiss(); - } + mController.requestDisallowLongPressAndDismiss(); } return super.onInterceptTouchEvent(ev); } public boolean requestScrollTo() { - findScrollContainer(); - mScrollContainer.lockScrollTo(mScrollContainerChild); + mController.lockScrollTo(mEntry); return true; } - private void findScrollContainer() { - if (mScrollContainer == null) { - mScrollContainerChild = null; - ViewParent p = this; - while (p != null) { - if (mScrollContainerChild == null && p instanceof ExpandableView) { - mScrollContainerChild = (View) p; - } - if (p.getParent() instanceof ScrollContainer) { - mScrollContainer = (ScrollContainer) p.getParent(); - if (mScrollContainerChild == null) { - mScrollContainerChild = (View) p; - } - break; - } - p = p.getParent(); - } - } - } - public boolean isActive() { return mEditText.isFocused() && mEditText.isEnabled(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java index 722874b07c97..f258fb19ff7d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java @@ -24,6 +24,7 @@ public interface RotationLockController extends Listenable, boolean isRotationLockAffordanceVisible(); boolean isRotationLocked(); void setRotationLocked(boolean locked); + void setRotationLockedAtAngle(boolean locked, int rotation); public interface RotationLockControllerCallback { void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java index 4f964964cd68..5418dc14e0bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java @@ -63,6 +63,10 @@ public final class RotationLockControllerImpl implements RotationLockController RotationPolicy.setRotationLock(mContext, locked); } + public void setRotationLockedAtAngle(boolean locked, int rotation){ + RotationPolicy.setRotationLockAtAngle(mContext, locked, rotation); + } + public boolean isRotationLockAffordanceVisible() { return RotationPolicy.isRotationLockToggleVisible(mContext); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java index 4cfd1c7fe6fd..91c208d571f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java @@ -297,7 +297,7 @@ public abstract class SignalController<T extends SignalController.State, .append("activityIn=").append(activityIn).append(',') .append("activityOut=").append(activityOut).append(',') .append("rssi=").append(rssi).append(',') - .append("lastModified=").append(DateFormat.format("MM-dd hh:mm:ss", time)); + .append("lastModified=").append(DateFormat.format("MM-dd HH:mm:ss", time)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java new file mode 100644 index 000000000000..c5067a6578eb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java @@ -0,0 +1,95 @@ +/* + * 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.policy; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.provider.Settings; +import android.util.KeyValueListParser; +import android.util.Log; + +import com.android.systemui.R; + +public final class SmartReplyConstants extends ContentObserver { + + private static final String TAG = "SmartReplyConstants"; + + private static final String KEY_ENABLED = "enabled"; + private static final String KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS = + "max_squeeze_remeasure_attempts"; + + private final boolean mDefaultEnabled; + private final int mDefaultMaxSqueezeRemeasureAttempts; + + private boolean mEnabled; + private int mMaxSqueezeRemeasureAttempts; + + private final Context mContext; + private final KeyValueListParser mParser = new KeyValueListParser(','); + + public SmartReplyConstants(Handler handler, Context context) { + super(handler); + + mContext = context; + final Resources resources = mContext.getResources(); + mDefaultEnabled = resources.getBoolean( + R.bool.config_smart_replies_in_notifications_enabled); + mDefaultMaxSqueezeRemeasureAttempts = resources.getInteger( + R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts); + + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS), + false, this); + updateConstants(); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + updateConstants(); + } + + private void updateConstants() { + synchronized (SmartReplyConstants.this) { + try { + mParser.setString(Settings.Global.getString(mContext.getContentResolver(), + Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS)); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Bad smart reply constants", e); + } + mEnabled = mParser.getBoolean(KEY_ENABLED, mDefaultEnabled); + mMaxSqueezeRemeasureAttempts = mParser.getInt( + KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS, mDefaultMaxSqueezeRemeasureAttempts); + } + } + + /** Returns whether smart replies in notifications are enabled. */ + public boolean isEnabled() { + return mEnabled; + } + + /** + * Returns the maximum number of times {@link SmartReplyView#onMeasure(int, int)} will try to + * find a better (narrower) line-break for a double-line smart reply button. + */ + public int getMaxSqueezeRemeasureAttempts() { + return mMaxSqueezeRemeasureAttempts; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java new file mode 100644 index 000000000000..57fc03cb7308 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -0,0 +1,68 @@ +package com.android.systemui.statusbar.policy; + +import android.app.PendingIntent; +import android.app.RemoteInput; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; + +import com.android.systemui.Dependency; +import com.android.systemui.R; + +/** View which displays smart reply buttons in notifications. */ +public class SmartReplyView extends LinearLayout { + + private static final String TAG = "SmartReplyView"; + + private final SmartReplyConstants mConstants; + + public SmartReplyView(Context context, AttributeSet attrs) { + super(context, attrs); + mConstants = Dependency.get(SmartReplyConstants.class); + } + + public void setRepliesFromRemoteInput(RemoteInput remoteInput, PendingIntent pendingIntent) { + removeAllViews(); + if (remoteInput != null && pendingIntent != null) { + CharSequence[] choices = remoteInput.getChoices(); + if (choices != null) { + for (CharSequence choice : choices) { + Button replyButton = inflateReplyButton( + getContext(), this, choice, remoteInput, pendingIntent); + addView(replyButton); + } + } + } + } + + public static SmartReplyView inflate(Context context, ViewGroup root) { + return (SmartReplyView) + LayoutInflater.from(context).inflate(R.layout.smart_reply_view, root, false); + } + + private static Button inflateReplyButton(Context context, ViewGroup root, CharSequence choice, + RemoteInput remoteInput, PendingIntent pendingIntent) { + Button b = (Button) LayoutInflater.from(context).inflate( + R.layout.smart_reply_button, root, false); + b.setText(choice); + b.setOnClickListener(view -> { + Bundle results = new Bundle(); + results.putString(remoteInput.getResultKey(), choice.toString()); + Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + RemoteInput.addResultsToIntent(new RemoteInput[]{remoteInput}, intent, results); + RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE); + try { + pendingIntent.send(context, 0, intent); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "Unable to send smart reply", e); + } + }); + return b; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TintedKeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TintedKeyButtonDrawable.java new file mode 100644 index 000000000000..acf9c00a111c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TintedKeyButtonDrawable.java @@ -0,0 +1,57 @@ +/* + * 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.policy; + +import android.annotation.ColorInt; +import android.graphics.drawable.Drawable; + +import com.android.internal.graphics.ColorUtils; + +/** + * Drawable for {@link KeyButtonView}s which contains a single asset and colors for light and dark + * navigation bar mode. + */ +public class TintedKeyButtonDrawable extends KeyButtonDrawable { + + private final int mLightColor; + private final int mDarkColor; + + public static TintedKeyButtonDrawable create(Drawable drawable, @ColorInt int lightColor, + @ColorInt int darkColor) { + return new TintedKeyButtonDrawable(new Drawable[] { drawable }, lightColor, darkColor); + } + + private TintedKeyButtonDrawable(Drawable[] drawables, int lightColor, int darkColor){ + super(drawables); + mLightColor = lightColor; + mDarkColor = darkColor; + } + + @Override + public void setDarkIntensity(float intensity) { + // Duplicate intensity scaling from KeyButtonDrawable + int intermediateColor = ColorUtils.compositeColors( + setAlphaFloat(mDarkColor, intensity), + setAlphaFloat(mLightColor,1f - intensity)); + getDrawable(0).setTint(intermediateColor); + invalidateSelf(); + } + + private int setAlphaFloat(int color, float alpha) { + return ColorUtils.setAlphaComponent(color, (int) (alpha * 255f)); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java index 527addf11da5..f5ae88b1788d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java @@ -154,7 +154,9 @@ public class UserInfoControllerImpl implements UserInfoController { avatar = new UserIconDrawable(avatarSize) .setIcon(rawAvatar).setBadgeIfManagedUser(mContext, userId).bake(); } else { - avatar = UserIcons.getDefaultUserIcon(isGuest? UserHandle.USER_NULL : userId, + avatar = UserIcons.getDefaultUserIcon( + context.getResources(), + isGuest? UserHandle.USER_NULL : userId, lightIcon); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 700c01a55ad6..7006d389cc34 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -747,7 +747,8 @@ public class UserSwitcherController { if (item.isAddUser) { return context.getDrawable(R.drawable.ic_add_circle_qs); } - Drawable icon = UserIcons.getDefaultUserIcon(item.resolveId(), /* light= */ false); + Drawable icon = UserIcons.getDefaultUserIcon( + context.getResources(), item.resolveId(), /* light= */ false); if (item.isGuest) { icon.setColorFilter(Utils.getColorAttr(context, android.R.attr.colorForeground), Mode.SRC_IN); @@ -910,6 +911,7 @@ public class UserSwitcherController { context.getString(android.R.string.cancel), this); setButton(DialogInterface.BUTTON_POSITIVE, context.getString(R.string.guest_exit_guest_dialog_remove), this); + SystemUIDialog.setWindowOnTop(this); setCanceledOnTouchOutside(false); mGuestId = guestId; mTargetId = targetId; @@ -937,6 +939,7 @@ public class UserSwitcherController { context.getString(android.R.string.cancel), this); setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok), this); + SystemUIDialog.setWindowOnTop(this); } @Override @@ -957,7 +960,7 @@ public class UserSwitcherController { } int id = user.id; Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon( - id, /* light= */ false)); + mContext.getResources(), id, /* light= */ false)); mUserManager.setUserIcon(id, icon); switchToUserId(id); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java index 4d8da441c039..d7a810eca02e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.stack; +import android.annotation.Nullable; import android.content.Context; import android.view.View; @@ -63,10 +64,12 @@ public class AmbientState { private boolean mPanelTracking; private boolean mExpansionChanging; private boolean mPanelFullWidth; - private Collection<HeadsUpManager.HeadsUpEntry> mPulsing; + private boolean mPulsing; private boolean mUnlockHintRunning; private boolean mQsCustomizerShowing; private int mIntrinsicPadding; + private int mExpandAnimationTopChange; + private ExpandableNotificationRow mExpandingNotification; public AmbientState(Context context) { reload(context); @@ -76,9 +79,25 @@ public class AmbientState { * Reload the dimens e.g. if the density changed. */ public void reload(Context context) { - mZDistanceBetweenElements = Math.max(1, context.getResources() + mZDistanceBetweenElements = getZDistanceBetweenElements(context); + mBaseZHeight = getBaseHeight(mZDistanceBetweenElements); + } + + private static int getZDistanceBetweenElements(Context context) { + return Math.max(1, context.getResources() .getDimensionPixelSize(R.dimen.z_distance_between_notifications)); - mBaseZHeight = 4 * mZDistanceBetweenElements; + } + + private static int getBaseHeight(int zdistanceBetweenElements) { + return 4 * zdistanceBetweenElements; + } + + /** + * @return the launch height for notifications that are launched + */ + public static int getNotificationLaunchHeight(Context context) { + int zDistance = getZDistanceBetweenElements(context); + return getBaseHeight(zDistance) * 2; } /** @@ -201,7 +220,8 @@ public class AmbientState { } public int getInnerHeight() { - return Math.max(Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding, mLayoutMinHeight); + return Math.max(Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding + - mExpandAnimationTopChange, mLayoutMinHeight); } public boolean isShadeExpanded() { @@ -236,6 +256,7 @@ public class AmbientState { mShelf = shelf; } + @Nullable public NotificationShelf getShelf() { return mShelf; } @@ -294,23 +315,18 @@ public class AmbientState { } public boolean hasPulsingNotifications() { - return mPulsing != null; + return mPulsing; } - public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> hasPulsing) { + public void setPulsing(boolean hasPulsing) { mPulsing = hasPulsing; } public boolean isPulsing(NotificationData.Entry entry) { - if (mPulsing == null) { + if (!mPulsing || mHeadsUpManager == null) { return false; } - for (HeadsUpManager.HeadsUpEntry e : mPulsing) { - if (e.entry == entry) { - return true; - } - } - return false; + return mHeadsUpManager.getAllEntries().anyMatch(e -> (e == entry)); } public boolean isPanelTracking() { @@ -378,4 +394,20 @@ public class AmbientState { public boolean isDozingAndNotPulsing(ExpandableNotificationRow row) { return isDark() && !isPulsing(row.getEntry()); } + + public void setExpandAnimationTopChange(int expandAnimationTopChange) { + mExpandAnimationTopChange = expandAnimationTopChange; + } + + public void setExpandingNotification(ExpandableNotificationRow row) { + mExpandingNotification = row; + } + + public ExpandableNotificationRow getExpandingNotification() { + return mExpandingNotification; + } + + public int getExpandAnimationTopChange() { + return mExpandAnimationTopChange; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java index ebb0a6d250cc..2f6e6584cc5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java @@ -36,7 +36,12 @@ public class AnimationProperties { * @return an animation filter for this animation. */ public AnimationFilter getAnimationFilter() { - return new AnimationFilter(); + return new AnimationFilter() { + @Override + public boolean shouldAnimateProperty(Property property) { + return true; + } + }; } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java index e0fd481c4cc2..3bf7d892ea0e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java @@ -95,6 +95,12 @@ public class ExpandableViewState extends ViewState { public boolean inShelf; /** + * A state indicating whether a headsup is currently fully visible, even when not scrolled. + * Only valid if the view is heads upped. + */ + public boolean headsUpIsVisible; + + /** * How much the child overlaps with the previous child on top. This is used to * show the background properly when the child on top is translating away. */ @@ -126,6 +132,7 @@ public class ExpandableViewState extends ViewState { clipTopAmount = svs.clipTopAmount; notGoneIndex = svs.notGoneIndex; location = svs.location; + headsUpIsVisible = svs.headsUpIsVisible; } } @@ -175,6 +182,10 @@ public class ExpandableViewState extends ViewState { expandableView.setTransformingInShelf(false); expandableView.setInShelf(inShelf); + + if (headsUpIsVisible) { + expandableView.setHeadsUpIsVisible(); + } } } @@ -229,6 +240,10 @@ public class ExpandableViewState extends ViewState { expandableView.setTransformingInShelf(true); } expandableView.setInShelf(this.inShelf); + + if (headsUpIsVisible) { + expandableView.setHeadsUpIsVisible(); + } } private void startHeightAnimation(final ExpandableView child, AnimationProperties properties) { @@ -452,4 +467,21 @@ public class ExpandableViewState extends ViewState { return getChildTag(view, TAG_END_HEIGHT); } } + + @Override + public void cancelAnimations(View view) { + super.cancelAnimations(view); + Animator animator = getChildTag(view, TAG_ANIMATOR_HEIGHT); + if (animator != null) { + animator.cancel(); + } + animator = getChildTag(view, TAG_ANIMATOR_SHADOW_ALPHA); + if (animator != null) { + animator.cancel(); + } + animator = getChildTag(view, TAG_ANIMATOR_TOP_INSET); + if (animator != null) { + animator.cancel(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java index fe53104ff9c2..4ca33cd3f601 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java @@ -128,12 +128,11 @@ public class NotificationChildrenContainer extends ViewGroup { mDividerHeight = res.getDimensionPixelSize( R.dimen.notification_children_container_divider_height); mDividerAlpha = res.getFloat(R.dimen.notification_divider_alpha); - mHeaderHeight = res.getDimensionPixelSize( - R.dimen.notification_children_container_header_height); mNotificationHeaderMargin = res.getDimensionPixelSize( R.dimen.notification_children_container_margin_top); mNotificatonTopPadding = res.getDimensionPixelSize( R.dimen.notification_children_container_top_padding); + mHeaderHeight = mNotificationHeaderMargin + mNotificatonTopPadding; mCollapsedBottompadding = res.getDimensionPixelSize( com.android.internal.R.dimen.notification_content_margin_bottom); mEnableShadowOnChildNotifications = @@ -395,7 +394,7 @@ public class NotificationChildrenContainer extends ViewGroup { } } else if (mOverflowNumber != null) { removeView(mOverflowNumber); - if (isShown()) { + if (isShown() && isAttachedToWindow()) { final View removedOverflowNumber = mOverflowNumber; addTransientView(removedOverflowNumber, getTransientViewCount()); CrossFadeHelper.fadeOut(removedOverflowNumber, new Runnable() { @@ -1260,4 +1259,17 @@ public class NotificationChildrenContainer extends ViewGroup { public boolean isUserLocked() { return mUserLocked; } + + public void setCurrentBottomRoundness(float currentBottomRoundness) { + boolean last = true; + for (int i = mChildren.size() - 1; i >= 0; i--) { + ExpandableNotificationRow child = mChildren.get(i); + if (child.getVisibility() == View.GONE) { + continue; + } + float bottomRoundness = last ? currentBottomRoundness : 0.0f; + child.setBottomRoundness(bottomRoundness, isShown() /* animate */); + last = false; + } + } } 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 75532d9e09be..1b55a5b0325f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.stack; +import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -29,7 +31,9 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.PointF; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; @@ -42,6 +46,7 @@ import android.support.annotation.VisibleForTesting; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.Log; +import android.util.MathUtils; import android.util.Pair; import android.util.Property; import android.view.ContextThemeWrapper; @@ -78,6 +83,8 @@ import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationGuts; +import com.android.systemui.statusbar.NotificationListContainer; +import com.android.systemui.statusbar.NotificationLogger; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationSnooze; import com.android.systemui.statusbar.StackScrollerDecorView; @@ -85,10 +92,11 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.ScrollAdapter; import android.support.v4.graphics.ColorUtils; @@ -108,8 +116,8 @@ import java.util.List; public class NotificationStackScrollLayout extends ViewGroup implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter, ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener, - NotificationMenuRowPlugin.OnMenuEventListener, ScrollContainer, - VisibilityLocationProvider { + NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider, + NotificationListContainer { public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; private static final String TAG = "StackScroller"; @@ -127,6 +135,7 @@ public class NotificationStackScrollLayout extends ViewGroup private boolean mSwipingInProgress; private int mCurrentStackHeight = Integer.MAX_VALUE; private final Paint mBackgroundPaint = new Paint(); + private final Path mBackgroundPath = new Path(); private final boolean mShouldDrawNotificationBackground; private float mExpandedHeight; @@ -154,7 +163,12 @@ public class NotificationStackScrollLayout extends ViewGroup private int mCollapsedSize; private int mPaddingBetweenElements; private int mIncreasedPaddingBetweenElements; + private int mRegularTopPadding; + private int mDarkTopPadding; + // Current padding, will be either mRegularTopPadding or mDarkTopPadding private int mTopPadding; + // Distance between AOD separator and shelf + private int mDarkSeparatorPadding; private int mBottomMargin; private int mBottomInset = 0; @@ -192,7 +206,7 @@ public class NotificationStackScrollLayout extends ViewGroup * The raw amount of the overScroll on the bottom, which is not rubber-banded. */ private float mOverScrolledBottomPixels; - private OnChildLocationsChangedListener mListener; + private NotificationLogger.OnChildLocationsChangedListener mListener; private OnOverscrollTopChangedListener mOverscrollTopChangedListener; private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; private OnEmptySpaceClickListener mOnEmptySpaceClickListener; @@ -240,7 +254,7 @@ public class NotificationStackScrollLayout extends ViewGroup * motion. */ private int mMaxScrollAfterExpand; - private SwipeHelper.LongPressListener mLongPressListener; + private ExpandableNotificationRow.LongPressListener mLongPressListener; private NotificationMenuRowPlugin mCurrMenuRow; private View mTranslatingParentView; @@ -275,7 +289,7 @@ public class NotificationStackScrollLayout extends ViewGroup private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>(); private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations = new HashSet<>(); - private HeadsUpManager mHeadsUpManager; + private HeadsUpManagerPhone mHeadsUpManager; private boolean mTrackingHeadsUp; private ScrimController mScrimController; private boolean mForceNoOverlappingRendering; @@ -345,24 +359,24 @@ public class NotificationStackScrollLayout extends ViewGroup } }; private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); - private Collection<HeadsUpManager.HeadsUpEntry> mPulsing; + private boolean mPulsing; private boolean mDrawBackgroundAsSrc; private boolean mFadingOut; private boolean mParentNotFullyVisible; private boolean mGroupExpandedForMeasure; private boolean mScrollable; private View mForcedScroll; - private float mBackgroundFadeAmount = 1.0f; - private static final Property<NotificationStackScrollLayout, Float> BACKGROUND_FADE = - new FloatProperty<NotificationStackScrollLayout>("backgroundFade") { + private float mDarkAmount = 0f; + private static final Property<NotificationStackScrollLayout, Float> DARK_AMOUNT = + new FloatProperty<NotificationStackScrollLayout>("darkAmount") { @Override public void setValue(NotificationStackScrollLayout object, float value) { - object.setBackgroundFadeAmount(value); + object.setDarkAmount(value); } @Override public Float get(NotificationStackScrollLayout object) { - return object.getBackgroundFadeAmount(); + return object.getDarkAmount(); } }; private boolean mUsingLightTheme; @@ -383,6 +397,13 @@ public class NotificationStackScrollLayout extends ViewGroup private int mCachedBackgroundColor; private boolean mHeadsUpGoingAwayAnimationsAllowed = true; private Runnable mAnimateScroll = this::animateScroll; + private int mCornerRadius; + private int mSidePaddings; + private final int mSeparatorWidth; + private final int mSeparatorThickness; + private final Rect mTmpRect = new Rect(); + private int mClockBottom; + private int mAntiBurnInOffsetX; public NotificationStackScrollLayout(Context context) { this(context, null); @@ -410,7 +431,6 @@ public class NotificationStackScrollLayout extends ViewGroup mExpandHelper.setEventSource(this); mExpandHelper.setScrollAdapter(this); mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext()); - mSwipeHelper.setLongPressListener(mLongPressListener); mStackScrollAlgorithm = createStackScrollAlgorithm(context); initView(context); mFalsingManager = FalsingManager.getInstance(context); @@ -418,8 +438,12 @@ public class NotificationStackScrollLayout extends ViewGroup res.getBoolean(R.bool.config_drawNotificationBackground); mFadeNotificationsOnDismiss = res.getBoolean(R.bool.config_fadeNotificationsOnDismiss); + mSeparatorWidth = res.getDimensionPixelSize(R.dimen.widget_separator_width); + mSeparatorThickness = res.getDimensionPixelSize(R.dimen.widget_separator_thickness); + mDarkSeparatorPadding = res.getDimensionPixelSize(R.dimen.widget_bottom_separator_padding); updateWillNotDraw(); + mBackgroundPaint.setAntiAlias(true); if (DEBUG) { mDebugPaint = new Paint(); mDebugPaint.setColor(0xffff0000); @@ -428,6 +452,7 @@ public class NotificationStackScrollLayout extends ViewGroup } } + @Override public NotificationSwipeActionHelper getSwipeActionHelper() { return mSwipeHelper; } @@ -465,10 +490,9 @@ public class NotificationStackScrollLayout extends ViewGroup } protected void onDraw(Canvas canvas) { - if (mShouldDrawNotificationBackground && !mAmbientState.isDark() - && mCurrentBounds.top < mCurrentBounds.bottom) { - canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, - mBackgroundPaint); + if (mShouldDrawNotificationBackground + && (mCurrentBounds.top < mCurrentBounds.bottom || mAmbientState.isDark())) { + drawBackground(canvas); } if (DEBUG) { @@ -481,17 +505,57 @@ public class NotificationStackScrollLayout extends ViewGroup } } + private void drawBackground(Canvas canvas) { + final int lockScreenLeft = mSidePaddings; + final int lockScreenRight = getWidth() - mSidePaddings; + final int lockScreenTop = mCurrentBounds.top; + final int lockScreenBottom = mCurrentBounds.bottom; + final int darkLeft = getWidth() / 2 - mSeparatorWidth / 2; + final int darkRight = darkLeft + mSeparatorWidth; + final int darkTop = (int) (mRegularTopPadding + mSeparatorThickness / 2f); + final int darkBottom = darkTop + mSeparatorThickness; + + if (mAmbientState.hasPulsingNotifications()) { + // TODO draw divider between notification and shelf + } else if (mAmbientState.isDark()) { + // Only draw divider on AOD if we actually have notifications + if (mFirstVisibleBackgroundChild != null) { + canvas.drawRect(darkLeft, darkTop, darkRight, darkBottom, mBackgroundPaint); + } + setClipBounds(null); + } else { + float animProgress = Interpolators.FAST_OUT_SLOW_IN + .getInterpolation(1f - mDarkAmount); + float sidePaddingsProgress = Interpolators.FAST_OUT_SLOW_IN + .getInterpolation((1f - mDarkAmount) * 2); + mTmpRect.set((int) MathUtils.lerp(darkLeft, lockScreenLeft, sidePaddingsProgress), + (int) MathUtils.lerp(darkTop, lockScreenTop, animProgress), + (int) MathUtils.lerp(darkRight, lockScreenRight, sidePaddingsProgress), + (int) MathUtils.lerp(darkBottom, lockScreenBottom, animProgress)); + canvas.drawRoundRect(mTmpRect.left, mTmpRect.top, mTmpRect.right, mTmpRect.bottom, + mCornerRadius, mCornerRadius, mBackgroundPaint); + setClipBounds(animProgress == 1 ? null : mTmpRect); + } + } + private void updateBackgroundDimming() { // No need to update the background color if it's not being drawn. if (!mShouldDrawNotificationBackground) { return; } - float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount); - alpha *= mBackgroundFadeAmount; - // We need to manually blend in the background color - int scrimColor = mScrimController.getBackgroundColor(); - int color = ColorUtils.blendARGB(scrimColor, mBgColor, alpha); + final int color; + if (mAmbientState.isDark()) { + color = Color.WHITE; + } else { + float alpha = + BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount); + alpha *= 1f - mDarkAmount; + // We need to manually blend in the background color + int scrimColor = mScrimController.getBackgroundColor(); + color = ColorUtils.blendARGB(scrimColor, mBgColor, alpha); + } + if (mCachedBackgroundColor != color) { mCachedBackgroundColor = color; mBackgroundPaint.setColor(color); @@ -521,8 +585,11 @@ public class NotificationStackScrollLayout extends ViewGroup R.dimen.min_top_overscroll_to_qs); mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height); mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom); + mSidePaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings); mMinInteractionHeight = res.getDimensionPixelSize( R.dimen.notification_min_interaction_height); + mCornerRadius = res.getDimensionPixelSize( + Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); } public void setDrawBackgroundAsSrc(boolean asSrc) { @@ -549,11 +616,15 @@ public class NotificationStackScrollLayout extends ViewGroup @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int width = MeasureSpec.getSize(widthMeasureSpec); + int childWidthSpec = MeasureSpec.makeMeasureSpec(width - mSidePaddings * 2, + MeasureSpec.getMode(widthMeasureSpec)); // We need to measure all children even the GONE ones, such that the heights are calculated // correctly as they are used to calculate how many we can fit on the screen. final int size = getChildCount(); for (int i = 0; i < size; i++) { - measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec); + measureChild(getChildAt(i), childWidthSpec, heightMeasureSpec); } } @@ -592,7 +663,9 @@ public class NotificationStackScrollLayout extends ViewGroup mNoAmbient = noAmbient; } - public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) { + @Override + public void setChildLocationsChangedListener( + NotificationLogger.OnChildLocationsChangedListener listener) { mListener = listener; } @@ -602,7 +675,7 @@ public class NotificationStackScrollLayout extends ViewGroup if (childViewState == null) { return false; } - if ((childViewState.location &= ExpandableViewState.VISIBLE_LOCATIONS) == 0) { + if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) { return false; } if (row.getVisibility() != View.VISIBLE) { @@ -618,6 +691,11 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateAlgorithmHeightAndPadding() { + if (mPulsing) { + mTopPadding = mClockBottom; + } else { + mTopPadding = mAmbientState.isDark() ? mDarkTopPadding : mRegularTopPadding; + } mAmbientState.setLayoutHeight(getLayoutHeight()); updateAlgorithmLayoutMinHeight(); mAmbientState.setTopPadding(mTopPadding); @@ -647,11 +725,32 @@ public class NotificationStackScrollLayout extends ViewGroup private void onPreDrawDuringAnimation() { mShelf.updateAppearance(); + updateClippingToTopRoundedCorner(); if (!mNeedsAnimation && !mChildrenUpdateRequested) { updateBackground(); } } + private void updateClippingToTopRoundedCorner() { + Float clipStart = (float) mTopPadding + mAmbientState.getExpandAnimationTopChange(); + Float clipEnd = clipStart + mCornerRadius; + boolean first = true; + for (int i = 0; i < getChildCount(); i++) { + ExpandableView child = (ExpandableView) getChildAt(i); + if (child.getVisibility() == GONE) { + continue; + } + float start = child.getTranslationY(); + float end = start + Math.max(child.getActualHeight() - child.getClipBottomAmount(), + 0); + boolean clip = clipStart > start && clipStart < end + || clipEnd >= start && clipEnd <= end; + clip &= !(first && mOwnScrollY == 0); + child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0) : -1); + first = false; + } + } + private void updateScrollStateForAddedChildren() { if (mChildrenToAddAnimated.isEmpty()) { return; @@ -719,8 +818,9 @@ public class NotificationStackScrollLayout extends ViewGroup } private void setTopPadding(int topPadding, boolean animate) { - if (mTopPadding != topPadding) { - mTopPadding = topPadding; + if (mRegularTopPadding != topPadding) { + mRegularTopPadding = topPadding; + mDarkTopPadding = topPadding + mDarkSeparatorPadding; updateAlgorithmHeightAndPadding(); updateContentHeight(); if (animate && mAnimationsEnabled && mIsExpanded) { @@ -821,6 +921,27 @@ public class NotificationStackScrollLayout extends ViewGroup } /** + * @return the height of the top heads up notification when pinned. This is different from the + * intrinsic height, which also includes whether the notification is system expanded and + * is mainly used when dragging down from a heads up notification. + */ + private int getTopHeadsUpPinnedHeight() { + NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry(); + if (topEntry == null) { + return 0; + } + ExpandableNotificationRow row = topEntry.row; + if (row.isChildInGroup()) { + final ExpandableNotificationRow groupSummary + = mGroupManager.getGroupSummary(row.getStatusBarNotification()); + if (groupSummary != null) { + row = groupSummary; + } + } + return row.getPinnedHeadsUpHeight(); + } + + /** * @return the position from where the appear transition ends when expanding. * Measured in absolute height. */ @@ -831,7 +952,7 @@ public class NotificationStackScrollLayout extends ViewGroup int minNotificationsForShelf = 1; if (mTrackingHeadsUp || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) { - appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight(); + appearPosition = getTopHeadsUpPinnedHeight(); minNotificationsForShelf = 2; } else { appearPosition = 0; @@ -884,8 +1005,7 @@ public class NotificationStackScrollLayout extends ViewGroup return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize; } - public void setLongPressListener(SwipeHelper.LongPressListener listener) { - mSwipeHelper.setLongPressListener(listener); + public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) { mLongPressListener = listener; } @@ -924,7 +1044,9 @@ public class NotificationStackScrollLayout extends ViewGroup mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey()); } } - performDismiss(v, mGroupManager, false /* fromAccessibility */); + if (v instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) v).performDismiss(false /* fromAccessibility */); + } mFalsingManager.onNotificationDismissed(); if (mFalsingManager.shouldEnforceBouncer()) { @@ -933,26 +1055,6 @@ public class NotificationStackScrollLayout extends ViewGroup } } - public static void performDismiss(View v, NotificationGroupManager groupManager, - boolean fromAccessibility) { - if (!(v instanceof ExpandableNotificationRow)) { - return; - } - ExpandableNotificationRow row = (ExpandableNotificationRow) v; - if (groupManager.isOnlyChildInGroup(row.getStatusBarNotification())) { - ExpandableNotificationRow groupSummary = - groupManager.getLogicalGroupSummary(row.getStatusBarNotification()); - if (groupSummary.isClearable()) { - performDismiss(groupSummary, groupManager, fromAccessibility); - } - } - row.setDismissed(true, fromAccessibility); - if (row.isClearable()) { - row.performDismiss(); - } - if (DEBUG) Log.v(TAG, "onChildDismissed: " + v); - } - @Override public void onChildSnappedBack(View animView, float targetLeft) { mAmbientState.onDragFinished(animView); @@ -1118,9 +1220,9 @@ public class NotificationStackScrollLayout extends ViewGroup if (slidingChild instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild; if (!mIsExpanded && row.isHeadsUp() && row.isPinned() - && mHeadsUpManager.getTopEntry().entry.row != row + && mHeadsUpManager.getTopEntry().row != row && mGroupManager.getGroupSummary( - mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification()) + mHeadsUpManager.getTopEntry().row.getStatusBarNotification()) != row) { continue; } @@ -1175,7 +1277,7 @@ public class NotificationStackScrollLayout extends ViewGroup if (v instanceof ExpandableNotificationRow) { ((ExpandableNotificationRow) v).setUserLocked(userLocked); } - removeLongPressCallback(); + cancelLongPress(); requestDisallowInterceptTouchEvent(true); } @@ -1197,7 +1299,6 @@ public class NotificationStackScrollLayout extends ViewGroup mScrollingEnabled = enable; } - @Override public void lockScrollTo(View v) { if (mForcedScroll == v) { return; @@ -1206,7 +1307,6 @@ public class NotificationStackScrollLayout extends ViewGroup scrollTo(v); } - @Override public boolean scrollTo(View v) { ExpandableView expandableView = (ExpandableView) v; int positionInLinearLayout = getPositionInLinearLayout(v); @@ -1306,6 +1406,7 @@ public class NotificationStackScrollLayout extends ViewGroup true /* isDismissAll */); } + @Override public void snapViewIfNeeded(ExpandableNotificationRow child) { boolean animate = mIsExpanded || isPinnedHeadsUp(child); // If the child is showing the notification menu snap to that @@ -1314,6 +1415,11 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override + public ViewGroup getViewParentForNotification(NotificationData.Entry entry) { + return this; + } + + @Override public boolean onTouchEvent(MotionEvent ev) { boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL || ev.getActionMasked()== MotionEvent.ACTION_UP; @@ -1345,7 +1451,7 @@ public class NotificationStackScrollLayout extends ViewGroup } // Check if we need to clear any snooze leavebehinds - NotificationGuts guts = mStatusBar.getExposedGuts(); + NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts(); if (guts != null && !isTouchInView(ev, guts) && guts.getGutsContent() instanceof NotificationSnooze) { NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent(); @@ -2034,8 +2140,9 @@ public class NotificationStackScrollLayout extends ViewGroup return mAmbientState.isPulsing(entry); } + @Override public boolean hasPulsingNotifications() { - return mPulsing != null; + return mPulsing; } private void updateScrollability() { @@ -2220,8 +2327,9 @@ public class NotificationStackScrollLayout extends ViewGroup return; } + final boolean awake = mDarkAmount != 0 || mAmbientState.isDark(); mScrimController.setExcludedBackgroundArea( - mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null + mFadingOut || mParentNotFullyVisible || awake || mIsClipped ? null : mCurrentBounds); invalidate(); } @@ -2238,6 +2346,8 @@ public class NotificationStackScrollLayout extends ViewGroup mBackgroundBounds.left = mTempInt2[0]; mBackgroundBounds.right = mTempInt2[0] + getWidth(); } + mBackgroundBounds.left += mSidePaddings; + mBackgroundBounds.right -= mSidePaddings; if (!mIsExpanded) { mBackgroundBounds.top = 0; mBackgroundBounds.bottom = 0; @@ -2510,12 +2620,13 @@ public class NotificationStackScrollLayout extends ViewGroup } // Check if we need to clear any snooze leavebehinds boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP; - NotificationGuts guts = mStatusBar.getExposedGuts(); + NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts(); if (!isTouchInView(ev, guts) && isUp && !swipeWantsIt && !expandWantsIt && !scrollWantsIt) { mCheckForLeavebehind = false; - mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */, - false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */); + mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */, + false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, + false /* resetMenu */); } if (ev.getActionMasked() == MotionEvent.ACTION_UP) { mCheckForLeavebehind = true; @@ -2566,10 +2677,7 @@ public class NotificationStackScrollLayout extends ViewGroup } } - /** - * Called when a notification is removed from the shade. This cleans up the state for a given - * view. - */ + @Override public void cleanUpViewState(View child) { if (child == mTranslatingParentView) { mTranslatingParentView = null; @@ -2581,7 +2689,7 @@ public class NotificationStackScrollLayout extends ViewGroup public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { super.requestDisallowInterceptTouchEvent(disallowIntercept); if (disallowIntercept) { - mSwipeHelper.removeLongPressCallback(); + cancelLongPress(); } } @@ -2667,7 +2775,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private boolean isClickedHeadsUp(View child) { - return HeadsUpManager.isClickedHeadsUpNotification(child); + return HeadsUpUtil.isClickedHeadsUpNotification(child); } /** @@ -2821,16 +2929,43 @@ public class NotificationStackScrollLayout extends ViewGroup private void updateFirstAndLastBackgroundViews() { ActivatableNotificationView firstChild = getFirstChildWithBackground(); ActivatableNotificationView lastChild = getLastChildWithBackground(); + boolean firstChanged = firstChild != mFirstVisibleBackgroundChild; + boolean lastChanged = lastChild != mLastVisibleBackgroundChild; if (mAnimationsEnabled && mIsExpanded) { - mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild; - mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild; + mAnimateNextBackgroundTop = firstChanged; + mAnimateNextBackgroundBottom = lastChanged; } else { mAnimateNextBackgroundTop = false; mAnimateNextBackgroundBottom = false; } + if (firstChanged && mFirstVisibleBackgroundChild != null + && !mFirstVisibleBackgroundChild.isRemoved()) { + mFirstVisibleBackgroundChild.setTopRoundness(0.0f, + mFirstVisibleBackgroundChild.isShown()); + } + if (lastChanged && mLastVisibleBackgroundChild != null + && !mLastVisibleBackgroundChild.isRemoved()) { + mLastVisibleBackgroundChild.setBottomRoundness(0.0f, + mLastVisibleBackgroundChild.isShown()); + } mFirstVisibleBackgroundChild = firstChild; mLastVisibleBackgroundChild = lastChild; mAmbientState.setLastVisibleBackgroundChild(lastChild); + applyRoundedNess(); + } + + private void applyRoundedNess() { + if (mFirstVisibleBackgroundChild != null) { + mFirstVisibleBackgroundChild.setTopRoundness(1.0f, + mFirstVisibleBackgroundChild.isShown() + && !mChildrenToAddAnimated.contains(mFirstVisibleBackgroundChild)); + } + if (mLastVisibleBackgroundChild != null) { + mLastVisibleBackgroundChild.setBottomRoundness(1.0f, + mLastVisibleBackgroundChild.isShown() + && !mChildrenToAddAnimated.contains(mLastVisibleBackgroundChild)); + } + invalidate(); } private void onViewAddedInternal(View child) { @@ -2848,10 +2983,12 @@ public class NotificationStackScrollLayout extends ViewGroup } } + @Override public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) { onViewRemovedInternal(row, childrenContainer); } + @Override public void notifyGroupChildAdded(View row) { onViewAddedInternal(row); } @@ -2877,6 +3014,17 @@ public class NotificationStackScrollLayout extends ViewGroup && (mIsExpanded || isPinnedHeadsUp(child)), child); } + @Override + public void setExpandingNotification(ExpandableNotificationRow row) { + mAmbientState.setExpandingNotification(row); + requestChildrenUpdate(); + } + + @Override + public void applyExpandAnimationParams(ExpandAnimationParameters params) { + mAmbientState.setExpandAnimationTopChange(params == null ? 0 : params.getTopChange()); + requestChildrenUpdate(); + } private void updateAnimationState(boolean running, View child) { if (child instanceof ExpandableNotificationRow) { @@ -2889,12 +3037,8 @@ public class NotificationStackScrollLayout extends ViewGroup return mNeedsAnimation && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty()); } - /** - * Generate an animation for an added child view. - * - * @param child The view to be added. - * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen. - */ + + @Override public void generateAddAnimation(View child, boolean fromMoreCard) { if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) { // Generate Animations @@ -2910,12 +3054,7 @@ public class NotificationStackScrollLayout extends ViewGroup } } - /** - * Change the position of child to a new location - * - * @param child the view to change the position for - * @param newIndex the new index - */ + @Override public void changeViewPosition(View child, int newIndex) { int currentIndex = indexOfChild(child); if (child != null && child.getParent() == this && currentIndex != newIndex) { @@ -2944,6 +3083,7 @@ public class NotificationStackScrollLayout extends ViewGroup mAnimationEvents.clear(); updateBackground(); updateViewShadows(); + updateClippingToTopRoundedCorner(); } else { applyCurrentState(); } @@ -3067,7 +3207,7 @@ public class NotificationStackScrollLayout extends ViewGroup } if (!childWasSwipedOut) { Rect clipBounds = child.getClipBounds(); - childWasSwipedOut = clipBounds.height() == 0; + childWasSwipedOut = clipBounds != null && clipBounds.height() == 0; } int animationType = childWasSwipedOut ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT @@ -3302,7 +3442,7 @@ public class NotificationStackScrollLayout extends ViewGroup mIsBeingDragged = isDragged; if (isDragged) { requestDisallowInterceptTouchEvent(true); - removeLongPressCallback(); + cancelLongPress(); } } @@ -3310,7 +3450,7 @@ public class NotificationStackScrollLayout extends ViewGroup public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); if (!hasWindowFocus) { - removeLongPressCallback(); + cancelLongPress(); } } @@ -3322,18 +3462,16 @@ public class NotificationStackScrollLayout extends ViewGroup } } - @Override public void requestDisallowLongPress() { - removeLongPressCallback(); + cancelLongPress(); } - @Override public void requestDisallowDismiss() { mDisallowDismissInThisMotion = true; } - public void removeLongPressCallback() { - mSwipeHelper.removeLongPressCallback(); + public void cancelLongPress() { + mSwipeHelper.cancelLongPress(); } @Override @@ -3357,8 +3495,9 @@ public class NotificationStackScrollLayout extends ViewGroup public void checkSnoozeLeavebehind() { if (mCheckForLeavebehind) { - mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */, - false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */); + mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */, + false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, + false /* resetMenu */); mCheckForLeavebehind = false; } } @@ -3608,6 +3747,7 @@ public class NotificationStackScrollLayout extends ViewGroup mHideSensitiveNeedsAnimation = true; mNeedsAnimation = true; } + updateContentHeight(); requestChildrenUpdate(); } } @@ -3631,12 +3771,13 @@ public class NotificationStackScrollLayout extends ViewGroup private void applyCurrentState() { mCurrentStackScrollState.apply(); if (mListener != null) { - mListener.onChildLocationsChanged(this); + mListener.onChildLocationsChanged(); } runAnimationFinishedRunnables(); setAnimationRunning(false); updateBackground(); updateViewShadows(); + updateClippingToTopRoundedCorner(); } private void updateViewShadows() { @@ -3741,43 +3882,43 @@ public class NotificationStackScrollLayout extends ViewGroup mDarkNeedsAnimation = true; mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation); mNeedsAnimation = true; - setBackgroundFadeAmount(0.0f); - } else if (!dark) { - setBackgroundFadeAmount(1.0f); - } - requestChildrenUpdate(); - if (dark) { - mScrimController.setExcludedBackgroundArea(null); } else { + setDarkAmount(dark ? 1f : 0f); updateBackground(); } - + requestChildrenUpdate(); + applyCurrentBackgroundBounds(); updateWillNotDraw(); updateContentHeight(); + updateAntiBurnInTranslation(); notifyHeightChangeListener(mShelf); } + private void updateAntiBurnInTranslation() { + setTranslationX(mAmbientState.isDark() ? mAntiBurnInOffsetX : 0); + } + /** * Updates whether or not this Layout will perform its own custom drawing (i.e. whether or * not {@link #onDraw(Canvas)} is called). This method should be called whenever the * {@link #mAmbientState}'s dark mode is toggled. */ private void updateWillNotDraw() { - boolean willDraw = !mAmbientState.isDark() && mShouldDrawNotificationBackground || DEBUG; + boolean willDraw = mShouldDrawNotificationBackground || DEBUG; setWillNotDraw(!willDraw); } - private void setBackgroundFadeAmount(float fadeAmount) { - mBackgroundFadeAmount = fadeAmount; + private void setDarkAmount(float darkAmount) { + mDarkAmount = darkAmount; updateBackgroundDimming(); } - public float getBackgroundFadeAmount() { - return mBackgroundFadeAmount; + public float getDarkAmount() { + return mDarkAmount; } private void startBackgroundFadeIn() { - ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, BACKGROUND_FADE, 0f, 1f); + ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, DARK_AMOUNT, mDarkAmount, 0f); fadeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP); fadeAnimator.setInterpolator(Interpolators.ALPHA_IN); fadeAnimator.start(); @@ -4115,11 +4256,31 @@ public class NotificationStackScrollLayout extends ViewGroup } } + @Override + public int getContainerChildCount() { + return getChildCount(); + } + + @Override + public View getContainerChildAt(int i) { + return getChildAt(i); + } + + @Override + public void removeContainerView(View v) { + removeView(v); + } + + @Override + public void addContainerView(View v) { + addView(v); + } + public void runAfterAnimationFinished(Runnable runnable) { mAnimationFinishedRunnables.add(runnable); } - public void setHeadsUpManager(HeadsUpManager headsUpManager) { + public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; mAmbientState.setHeadsUpManager(headsUpManager); } @@ -4187,13 +4348,15 @@ public class NotificationStackScrollLayout extends ViewGroup return mIsExpanded; } - public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) { - if (mPulsing == null && pulsing == null) { + public void setPulsing(boolean pulsing, int clockBottom) { + if (!mPulsing && !pulsing) { return; } mPulsing = pulsing; + mClockBottom = clockBottom; mAmbientState.setPulsing(pulsing); updateNotificationAnimationStates(); + updateAlgorithmHeightAndPadding(); updateContentHeight(); notifyHeightChangeListener(mShelf); requestChildrenUpdate(); @@ -4316,15 +4479,16 @@ public class NotificationStackScrollLayout extends ViewGroup mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed; } - public void setDarkShelfOffsetX(int shelfOffsetX) { - mShelf.setDarkOffsetX(shelfOffsetX); + public void setAntiBurnInOffsetX(int antiBurnInOffsetX) { + mAntiBurnInOffsetX = antiBurnInOffsetX; + updateAntiBurnInTranslation(); } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s" + " alpha:%f scrollY:%d]", this.getClass().getSimpleName(), - mPulsing != null ?"T":"f", + mPulsing ? "T":"f", mAmbientState.isQsCustomizerShowing() ? "T":"f", getVisibility() == View.VISIBLE ? "visible" : getVisibility() == View.GONE ? "gone" @@ -4333,17 +4497,6 @@ public class NotificationStackScrollLayout extends ViewGroup mAmbientState.getScrollY())); } - public void setTouchActive(boolean touchActive) { - mShelf.setTouchActive(touchActive); - } - - /** - * A listener that is notified when some child locations might have changed. - */ - public interface OnChildLocationsChangedListener { - void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout); - } - /** * A listener that is notified when the empty space below the notifications is clicked on */ @@ -4440,8 +4593,9 @@ public class NotificationStackScrollLayout extends ViewGroup // of the panel early. handleChildDismissed(view); } - mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */, - false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */); + mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */, + false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, + false /* resetMenu */); handleMenuCoveredOrDismissed(); } @@ -4526,7 +4680,7 @@ public class NotificationStackScrollLayout extends ViewGroup } public void closeControlsIfOutsideTouch(MotionEvent ev) { - NotificationGuts guts = mStatusBar.getExposedGuts(); + NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts(); View view = null; if (guts != null && !guts.getGutsContent().isLeavebehind()) { // Only close visible guts if they're not a leavebehind. @@ -4538,8 +4692,9 @@ public class NotificationStackScrollLayout extends ViewGroup } if (view != null && !isTouchInView(ev, view)) { // Touch was outside visible guts / menu notification, close what's visible - mStatusBar.closeAndSaveGuts(false /* removeLeavebehind */, false /* force */, - true /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */); + mStatusBar.getGutsManager().closeAndSaveGuts(false /* removeLeavebehind */, + false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, + false /* resetMenu */); resetExposedMenuView(true /* animate */, true /* force */); } } @@ -4597,6 +4752,7 @@ public class NotificationStackScrollLayout extends ViewGroup } } + @Override public void resetExposedMenuView(boolean animate, boolean force) { mSwipeHelper.resetExposedMenuView(animate, force); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java deleted file mode 100644 index b9d12ce8f151..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.stack; - -import android.view.View; - -/** - * Interface for container layouts that scroll and listen for long presses. A child that - * wants to handle long press can use this to cancel the parents long press logic or request - * to be made visible by scrolling to it. - */ -public interface ScrollContainer { - /** - * Request that the view does not perform long press for the current touch. - */ - void requestDisallowLongPress(); - - /** - * Request that the view is made visible by scrolling to it. - * Return true if it scrolls. - */ - boolean scrollTo(View v); - - /** - * Like {@link #scrollTo(View)}, but keeps the scroll locked until the user - * scrolls, or {@param v} loses focus or is detached. - */ - void lockScrollTo(View v); - - /** - * Request that the view does not dismiss for the current touch. - */ - void requestDisallowDismiss(); -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java index c060b0882060..d68a7b1dc205 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -122,20 +122,23 @@ public class StackScrollAlgorithm { } private void updateShelfState(StackScrollState resultState, AmbientState ambientState) { NotificationShelf shelf = ambientState.getShelf(); - shelf.updateState(resultState, ambientState); + if (shelf != null) { + shelf.updateState(resultState, ambientState); + } } private void updateClipping(StackScrollState resultState, StackScrollAlgorithmState algorithmState, AmbientState ambientState) { float drawStart = !ambientState.isOnKeyguard() ? ambientState.getTopPadding() - + ambientState.getStackTranslation() : 0; + + ambientState.getStackTranslation() + ambientState.getExpandAnimationTopChange() + : 0; float previousNotificationEnd = 0; float previousNotificationStart = 0; int childCount = algorithmState.visibleChildren.size(); for (int i = 0; i < childCount; i++) { ExpandableView child = algorithmState.visibleChildren.get(i); ExpandableViewState state = resultState.getViewStateForView(child); - if (!child.mustStayOnScreen()) { + if (!child.mustStayOnScreen() || state.headsUpIsVisible) { previousNotificationEnd = Math.max(drawStart, previousNotificationEnd); previousNotificationStart = Math.max(drawStart, previousNotificationStart); } @@ -318,6 +321,10 @@ public class StackScrollAlgorithm { lastView = v; } } + ExpandableNotificationRow expandingNotification = ambientState.getExpandingNotification(); + state.indexOfExpandingNotification = expandingNotification != null + ? state.visibleChildren.indexOf(expandingNotification) + : -1; } private float getPaddingForValue(Float increasedPadding) { @@ -378,6 +385,16 @@ public class StackScrollAlgorithm { boolean isEmptyShadeView = child instanceof EmptyShadeView; childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA; + float inset = ambientState.getTopPadding() + ambientState.getStackTranslation(); + if (i < algorithmState.getIndexOfExpandingNotification()) { + inset += ambientState.getExpandAnimationTopChange(); + } + if (child.mustStayOnScreen() && childViewState.yTranslation >= 0) { + // Even if we're not scrolled away we're in view and we're also not in the + // shelf. We can relax the constraints and let us scroll off the top! + float end = childViewState.yTranslation + childViewState.height + inset; + childViewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation(); + } if (isDismissView) { childViewState.yTranslation = Math.min(childViewState.yTranslation, ambientState.getInnerHeight() - childHeight); @@ -385,7 +402,7 @@ public class StackScrollAlgorithm { childViewState.yTranslation = ambientState.getInnerHeight() - childHeight + ambientState.getStackTranslation() * 0.25f; } else { - clampPositionToShelf(childViewState, ambientState); + clampPositionToShelf(child, childViewState, ambientState); } currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild; @@ -396,8 +413,7 @@ public class StackScrollAlgorithm { Log.wtf(LOG_TAG, "Failed to assign location for child " + i); } - childViewState.yTranslation += ambientState.getTopPadding() - + ambientState.getStackTranslation(); + childViewState.yTranslation += inset; return currentYPosition; } @@ -420,19 +436,21 @@ public class StackScrollAlgorithm { break; } ExpandableViewState childState = resultState.getViewStateForView(row); - if (topHeadsUpEntry == null) { + if (topHeadsUpEntry == null && row.mustStayOnScreen() && !childState.headsUpIsVisible) { topHeadsUpEntry = row; childState.location = ExpandableViewState.LOCATION_FIRST_HUN; } boolean isTopEntry = topHeadsUpEntry == row; float unmodifiedEndLocation = childState.yTranslation + childState.height; if (mIsExpanded) { - // Ensure that the heads up is always visible even when scrolled off - clampHunToTop(ambientState, row, childState); - if (i == 0 && ambientState.isAboveShelf(row)) { - // the first hun can't get off screen. - clampHunToMaxTranslation(ambientState, row, childState); - childState.hidden = false; + if (row.mustStayOnScreen() && !childState.headsUpIsVisible) { + // Ensure that the heads up is always visible even when scrolled off + clampHunToTop(ambientState, row, childState); + if (i == 0 && ambientState.isAboveShelf(row)) { + // the first hun can't get off screen. + clampHunToMaxTranslation(ambientState, row, childState); + childState.hidden = false; + } } } if (row.isPinned()) { @@ -440,7 +458,7 @@ public class StackScrollAlgorithm { childState.height = Math.max(row.getIntrinsicHeight(), childState.height); childState.hidden = false; ExpandableViewState topState = resultState.getViewStateForView(topHeadsUpEntry); - if (!isTopEntry && (!mIsExpanded + if (topState != null && !isTopEntry && (!mIsExpanded || unmodifiedEndLocation < topState.yTranslation + topState.height)) { // Ensure that a headsUp doesn't vertically extend further than the heads-up at // the top most z-position @@ -482,17 +500,24 @@ public class StackScrollAlgorithm { * Clamp the height of the child down such that its end is at most on the beginning of * the shelf. * + * @param child * @param childViewState the view state of the child * @param ambientState the ambient state */ - private void clampPositionToShelf(ExpandableViewState childViewState, + private void clampPositionToShelf(ExpandableView child, + ExpandableViewState childViewState, AmbientState ambientState) { + if (ambientState.getShelf() == null) { + return; + } + int shelfStart = ambientState.getInnerHeight() - ambientState.getShelf().getIntrinsicHeight(); childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart); if (childViewState.yTranslation >= shelfStart) { - childViewState.hidden = true; + childViewState.hidden = !child.isExpandAnimationRunning(); childViewState.inShelf = true; + childViewState.headsUpIsVisible = false; } if (!ambientState.isShadeExpanded()) { childViewState.height = (int) (mStatusBarHeight - childViewState.yTranslation); @@ -531,7 +556,8 @@ public class StackScrollAlgorithm { ExpandableViewState childViewState = resultState.getViewStateForView(child); int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements(); float baseZ = ambientState.getBaseZHeight(); - if (child.mustStayOnScreen() && !ambientState.isDozingAndNotPulsing(child) + if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible + && !ambientState.isDozingAndNotPulsing(child) && childViewState.yTranslation < ambientState.getTopPadding() + ambientState.getStackTranslation()) { if (childrenOnTop != 0.0f) { @@ -546,7 +572,8 @@ public class StackScrollAlgorithm { } else if (i == 0 && ambientState.isAboveShelf(child)) { // In case this is a new view that has never been measured before, we don't want to // elevate if we are currently expanded more then the notification - int shelfHeight = ambientState.getShelf().getIntrinsicHeight(); + int shelfHeight = ambientState.getShelf() == null ? 0 : + ambientState.getShelf().getIntrinsicHeight(); float shelfStart = ambientState.getInnerHeight() - shelfHeight + ambientState.getTopPadding() + ambientState.getStackTranslation(); @@ -585,6 +612,7 @@ public class StackScrollAlgorithm { * The padding after each child measured in pixels. */ public final HashMap<ExpandableView, Float> paddingMap = new HashMap<>(); + private int indexOfExpandingNotification; public int getPaddingAfterChild(ExpandableView child) { Float padding = paddingMap.get(child); @@ -594,6 +622,10 @@ public class StackScrollAlgorithm { } return (int) padding.floatValue(); } + + public int getIndexOfExpandingNotification() { + return indexOfExpandingNotification; + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java index e3c746bf0d32..588b75881a4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java @@ -84,6 +84,7 @@ public class StackScrollState { viewState.scaleX = view.getScaleX(); viewState.scaleY = view.getScaleY(); viewState.inShelf = false; + viewState.headsUpIsVisible = false; } public ExpandableViewState getViewStateForView(View requestedView) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java index f78a718eff9f..236c348e539b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -40,7 +40,7 @@ import java.util.Stack; public class StackStateAnimator { public static final int ANIMATION_DURATION_STANDARD = 360; - public static final int ANIMATION_DURATION_WAKEUP = 200; + public static final int ANIMATION_DURATION_WAKEUP = 500; public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448; public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464; public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java index 27b730cdb6c7..04a7bd79c6ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java @@ -21,7 +21,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; -import android.app.Notification; import android.util.Property; import android.view.View; import android.view.animation.Interpolator; @@ -29,9 +28,9 @@ import android.view.animation.Interpolator; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableView; -import com.android.systemui.statusbar.NotificationShelf; +import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.HeadsUpUtil; /** * A state of a view. This can be used to apply a set of view properties to a view with @@ -64,8 +63,8 @@ public class ViewState { private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag; private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag; - private static final PropertyAnimator.AnimatableProperty SCALE_X_PROPERTY - = new PropertyAnimator.AnimatableProperty() { + private static final AnimatableProperty SCALE_X_PROPERTY + = new AnimatableProperty() { @Override public int getAnimationStartTag() { @@ -88,8 +87,8 @@ public class ViewState { } }; - private static final PropertyAnimator.AnimatableProperty SCALE_Y_PROPERTY - = new PropertyAnimator.AnimatableProperty() { + private static final AnimatableProperty SCALE_Y_PROPERTY + = new AnimatableProperty() { @Override public int getAnimationStartTag() { @@ -251,7 +250,7 @@ public class ViewState { return getChildTag(view, tag) != null; } - public static boolean isAnimating(View view, PropertyAnimator.AnimatableProperty property) { + public static boolean isAnimating(View view, AnimatableProperty property) { return getChildTag(view, property.getAnimatorTag()) != null; } @@ -403,7 +402,7 @@ public class ViewState { startZTranslationAnimation(view, NO_NEW_ANIMATIONS); } - private void updateAnimation(View view, PropertyAnimator.AnimatableProperty property, + private void updateAnimation(View view, AnimatableProperty property, float endValue) { PropertyAnimator.startAnimation(view, property, endValue, NO_NEW_ANIMATIONS); } @@ -583,7 +582,7 @@ public class ViewState { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - HeadsUpManager.setIsClickedNotification(child, false); + HeadsUpUtil.setIsClickedHeadsUpNotification(child, false); child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null); child.setTag(TAG_START_TRANSLATION_Y, null); child.setTag(TAG_END_TRANSLATION_Y, null); diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java index 45abd456ea16..eb0c89b06528 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java @@ -18,7 +18,7 @@ import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_END; import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_START; import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_IMAGE_DELIM; -import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.MENU_IME; +import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.MENU_IME_ROTATE; import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAVSPACE; import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_LEFT; import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_RIGHT; @@ -29,24 +29,14 @@ import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.ext import android.annotation.Nullable; import android.app.AlertDialog; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.res.Resources; import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.FontMetricsInt; -import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.Handler; -import android.support.v14.preference.PreferenceFragment; -import android.support.v7.preference.DropDownPreference; import android.support.v7.preference.ListPreference; import android.support.v7.preference.Preference; import android.support.v7.preference.Preference.OnPreferenceChangeListener; -import android.support.v7.preference.Preference.OnPreferenceClickListener; -import android.support.v7.preference.PreferenceCategory; import android.text.SpannableStringBuilder; import android.text.style.ImageSpan; import android.util.Log; @@ -56,7 +46,6 @@ import android.widget.EditText; import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.statusbar.phone.NavigationBarInflaterView; import com.android.systemui.tuner.TunerService.Tunable; import java.util.ArrayList; @@ -100,7 +89,7 @@ public class NavBarTuner extends TunerPreferenceFragment { addPreferencesFromResource(R.xml.nav_bar_tuner); bindLayout((ListPreference) findPreference(LAYOUT)); bindButton(NAV_BAR_LEFT, NAVSPACE, LEFT); - bindButton(NAV_BAR_RIGHT, MENU_IME, RIGHT); + bindButton(NAV_BAR_RIGHT, MENU_IME_ROTATE, RIGHT); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java index f91e45d05e79..27bf534c290e 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java @@ -169,16 +169,23 @@ public class PluginFragment extends PreferenceFragment { protected boolean persistBoolean(boolean value) { final int desiredState = value ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; + boolean shouldSendBroadcast = false; for (int i = 0; i < mInfo.services.length; i++) { ComponentName componentName = new ComponentName(mInfo.packageName, mInfo.services[i].name); - mPm.setComponentEnabledSetting(componentName, desiredState, - PackageManager.DONT_KILL_APP); + + if (mPm.getComponentEnabledSetting(componentName) != desiredState) { + mPm.setComponentEnabledSetting(componentName, desiredState, + PackageManager.DONT_KILL_APP); + shouldSendBroadcast = true; + } + } + if (shouldSendBroadcast) { + final String pkg = mInfo.packageName; + final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED, + pkg != null ? Uri.fromParts("package", pkg, null) : null); + getContext().sendBroadcast(intent); } - final String pkg = mInfo.packageName; - final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED, - pkg != null ? Uri.fromParts("package", pkg, null) : null); - getContext().sendBroadcast(intent); return true; } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java index 8e584bc362eb..5a4478f072e0 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java @@ -58,7 +58,7 @@ public class TunerServiceImpl extends TunerService { private static final String TUNER_VERSION = "sysui_tuner_version"; - private static final int CURRENT_TUNER_VERSION = 1; + private static final int CURRENT_TUNER_VERSION = 2; private final Observer mObserver = new Observer(); // Map of Uris we listen on to their settings keys. @@ -116,6 +116,9 @@ public class TunerServiceImpl extends TunerService { TextUtils.join(",", iconBlacklist), mCurrentUser); } } + if (oldVersion < 2) { + setTunerEnabled(mContext, false); + } setValue(TUNER_VERSION, newVersion); } diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java index 3eccccdd75bf..0a3e34ee951d 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java @@ -68,13 +68,14 @@ public class UsbConfirmActivity extends AlertActivity String appName = mResolveInfo.loadLabel(packageManager).toString(); final AlertController.AlertParams ap = mAlertParams; - ap.mIcon = mResolveInfo.loadIcon(packageManager); ap.mTitle = appName; if (mDevice == null) { - ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName); + ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName, + mAccessory.getDescription()); mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory); } else { - ap.mMessage = getString(R.string.usb_device_confirm_prompt, appName); + ap.mMessage = getString(R.string.usb_device_confirm_prompt, appName, + mDevice.getProductName()); mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice); } ap.mPositiveButtonText = getString(android.R.string.ok); @@ -88,9 +89,11 @@ public class UsbConfirmActivity extends AlertActivity ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null); mAlwaysUse = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse); if (mDevice == null) { - mAlwaysUse.setText(R.string.always_use_accessory); + mAlwaysUse.setText(getString(R.string.always_use_accessory, appName, + mAccessory.getDescription())); } else { - mAlwaysUse.setText(R.string.always_use_device); + mAlwaysUse.setText(getString(R.string.always_use_device, appName, + mDevice.getProductName())); } mAlwaysUse.setOnCheckedChangeListener(this); mClearDefaultHint = (TextView)ap.mView.findViewById( diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java index 1e69fc5ce1fc..238407a9a6f1 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java @@ -16,13 +16,17 @@ package com.android.systemui.usb; +import android.annotation.NonNull; import android.app.AlertDialog; import android.app.PendingIntent; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.res.XmlResourceParser; import android.hardware.usb.IUsbManager; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; @@ -41,8 +45,13 @@ import android.widget.TextView; import com.android.internal.app.AlertActivity; import com.android.internal.app.AlertController; +import com.android.internal.util.XmlUtils; +import android.hardware.usb.AccessoryFilter; +import android.hardware.usb.DeviceFilter; import com.android.systemui.R; +import org.xmlpull.v1.XmlPullParser; + public class UsbPermissionActivity extends AlertActivity implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener { @@ -81,13 +90,14 @@ public class UsbPermissionActivity extends AlertActivity String appName = aInfo.loadLabel(packageManager).toString(); final AlertController.AlertParams ap = mAlertParams; - ap.mIcon = aInfo.loadIcon(packageManager); ap.mTitle = appName; if (mDevice == null) { - ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName); + ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName, + mAccessory.getDescription()); mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory); } else { - ap.mMessage = getString(R.string.usb_device_permission_prompt, appName); + ap.mMessage = getString(R.string.usb_device_permission_prompt, appName, + mDevice.getProductName()); mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice); } ap.mPositiveButtonText = getString(android.R.string.ok); @@ -95,25 +105,123 @@ public class UsbPermissionActivity extends AlertActivity ap.mPositiveButtonListener = this; ap.mNegativeButtonListener = this; - // add "always use" checkbox - LayoutInflater inflater = (LayoutInflater)getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null); - mAlwaysUse = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse); - if (mDevice == null) { - mAlwaysUse.setText(R.string.always_use_accessory); - } else { - mAlwaysUse.setText(R.string.always_use_device); + try { + PackageInfo packageInfo = packageManager.getPackageInfo(mPackageName, + PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA); + + if ((mDevice != null && canBeDefault(mDevice, packageInfo)) + || (mAccessory != null && canBeDefault(mAccessory, packageInfo))) { + // add "open when" checkbox + LayoutInflater inflater = (LayoutInflater) getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null); + mAlwaysUse = (CheckBox) ap.mView.findViewById(com.android.internal.R.id.alwaysUse); + if (mDevice == null) { + mAlwaysUse.setText(getString(R.string.always_use_accessory, appName, + mAccessory.getDescription())); + } else { + mAlwaysUse.setText(getString(R.string.always_use_device, appName, + mDevice.getProductName())); + } + mAlwaysUse.setOnCheckedChangeListener(this); + + mClearDefaultHint = (TextView)ap.mView.findViewById( + com.android.internal.R.id.clearDefaultHint); + mClearDefaultHint.setVisibility(View.GONE); + } + } catch (PackageManager.NameNotFoundException e) { + // ignore } - mAlwaysUse.setOnCheckedChangeListener(this); - mClearDefaultHint = (TextView)ap.mView.findViewById( - com.android.internal.R.id.clearDefaultHint); - mClearDefaultHint.setVisibility(View.GONE); setupAlert(); } + /** + * Can the app be the default for the USB device. I.e. can the app be launched by default if + * the device is plugged in. + * + * @param device The device the app would be default for + * @param packageInfo The package info of the app + * + * @return {@code true} iff the app can be default + */ + private boolean canBeDefault(@NonNull UsbDevice device, @NonNull PackageInfo packageInfo) { + ActivityInfo[] activities = packageInfo.activities; + if (activities != null) { + int numActivities = activities.length; + for (int i = 0; i < numActivities; i++) { + ActivityInfo activityInfo = activities[i]; + + try (XmlResourceParser parser = activityInfo.loadXmlMetaData(getPackageManager(), + UsbManager.ACTION_USB_DEVICE_ATTACHED)) { + if (parser == null) { + continue; + } + + XmlUtils.nextElement(parser); + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + if ("usb-device".equals(parser.getName())) { + DeviceFilter filter = DeviceFilter.read(parser); + if (filter.matches(device)) { + return true; + } + } + + XmlUtils.nextElement(parser); + } + } catch (Exception e) { + Log.w(TAG, "Unable to load component info " + activityInfo.toString(), e); + } + } + } + + return false; + } + + /** + * Can the app be the default for the USB accessory. I.e. can the app be launched by default if + * the accessory is plugged in. + * + * @param accessory The accessory the app would be default for + * @param packageInfo The package info of the app + * + * @return {@code true} iff the app can be default + */ + private boolean canBeDefault(@NonNull UsbAccessory accessory, + @NonNull PackageInfo packageInfo) { + ActivityInfo[] activities = packageInfo.activities; + if (activities != null) { + int numActivities = activities.length; + for (int i = 0; i < numActivities; i++) { + ActivityInfo activityInfo = activities[i]; + + try (XmlResourceParser parser = activityInfo.loadXmlMetaData(getPackageManager(), + UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) { + if (parser == null) { + continue; + } + + XmlUtils.nextElement(parser); + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + if ("usb-accessory".equals(parser.getName())) { + AccessoryFilter filter = AccessoryFilter.read(parser); + if (filter.matches(accessory)) { + return true; + } + } + + XmlUtils.nextElement(parser); + } + } catch (Exception e) { + Log.w(TAG, "Unable to load component info " + activityInfo.toString(), e); + } + } + } + + return false; + } + @Override public void onDestroy() { IBinder b = ServiceManager.getService(USB_SERVICE); @@ -126,7 +234,7 @@ public class UsbPermissionActivity extends AlertActivity intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice); if (mPermissionGranted) { service.grantDevicePermission(mDevice, mUid); - if (mAlwaysUse.isChecked()) { + if (mAlwaysUse != null && mAlwaysUse.isChecked()) { final int userId = UserHandle.getUserId(mUid); service.setDevicePackage(mDevice, mPackageName, userId); } @@ -136,7 +244,7 @@ public class UsbPermissionActivity extends AlertActivity intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory); if (mPermissionGranted) { service.grantAccessoryPermission(mAccessory, mUid); - if (mAlwaysUse.isChecked()) { + if (mAlwaysUse != null && mAlwaysUse.isChecked()) { final int userId = UserHandle.getUserId(mUid); service.setAccessoryPackage(mAccessory, mPackageName, userId); } diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java index 87bc0e67f7be..14d5c6f52cd0 100644 --- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java +++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java @@ -31,7 +31,8 @@ import java.util.Arrays; public class NotificationChannels extends SystemUI { public static String ALERTS = "ALR"; - public static String SCREENSHOTS = "SCN"; + public static String SCREENSHOTS_LEGACY = "SCN"; + public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP"; public static String GENERAL = "GEN"; public static String STORAGE = "DSK"; public static String TVPIP = "TPP"; @@ -56,10 +57,6 @@ public class NotificationChannels extends SystemUI { context.getString(R.string.notification_channel_alerts), NotificationManager.IMPORTANCE_HIGH), new NotificationChannel( - SCREENSHOTS, - context.getString(R.string.notification_channel_screenshot), - NotificationManager.IMPORTANCE_LOW), - new NotificationChannel( GENERAL, context.getString(R.string.notification_channel_general), NotificationManager.IMPORTANCE_MIN), @@ -69,9 +66,18 @@ public class NotificationChannels extends SystemUI { isTv(context) ? NotificationManager.IMPORTANCE_DEFAULT : NotificationManager.IMPORTANCE_LOW), + createScreenshotChannel( + context.getString(R.string.notification_channel_screenshot), + nm.getNotificationChannel(SCREENSHOTS_LEGACY)), batteryChannel )); + // Delete older SS channel if present. + // Screenshots promoted to heads-up in P, this cleans up the lower priority channel from O. + // This line can be deleted in Q. + nm.deleteNotificationChannel(SCREENSHOTS_LEGACY); + + if (isTv(context)) { // TV specific notification channel for TV PIP controls. // Importance should be {@link NotificationManager#IMPORTANCE_MAX} to have the highest @@ -83,6 +89,40 @@ public class NotificationChannels extends SystemUI { } } + /** + * Set up screenshot channel, respecting any previously committed user settings on legacy + * channel. + * @return + */ + @VisibleForTesting static NotificationChannel createScreenshotChannel( + String name, NotificationChannel legacySS) { + NotificationChannel screenshotChannel = new NotificationChannel(SCREENSHOTS_HEADSUP, + name, NotificationManager.IMPORTANCE_HIGH); // pop on screen + + screenshotChannel.setSound(Uri.parse(""), // silent + new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build()); + + if (legacySS != null) { + // Respect any user modified fields from the old channel. + int userlock = legacySS.getUserLockedFields(); + if ((userlock & NotificationChannel.USER_LOCKED_IMPORTANCE) != 0) { + screenshotChannel.setImportance(legacySS.getImportance()); + } + if ((userlock & NotificationChannel.USER_LOCKED_SOUND) != 0) { + screenshotChannel.setSound(legacySS.getSound(), legacySS.getAudioAttributes()); + } + if ((userlock & NotificationChannel.USER_LOCKED_VIBRATION) != 0) { + screenshotChannel.setVibrationPattern(legacySS.getVibrationPattern()); + } + if ((userlock & NotificationChannel.USER_LOCKED_LIGHTS) != 0) { + screenshotChannel.setLightColor(legacySS.getLightColor()); + } + // skip show_badge, irrelevant for system channel + } + + return screenshotChannel; + } + @Override public void start() { createAll(mContext); diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index f4aebae7bdaf..eca612776f21 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -14,6 +14,11 @@ package com.android.systemui.util; +import android.view.View; + +import com.android.systemui.SysUiServiceProvider; +import com.android.systemui.statusbar.CommandQueue; + import java.util.List; import java.util.function.Consumer; @@ -28,4 +33,52 @@ public class Utils { c.accept(list.get(i)); } } + + /** + * Sets the visibility of an UI element according to the DISABLE_* flags in + * {@link android.app.StatusBarManager}. + */ + public static class DisableStateTracker implements CommandQueue.Callbacks, + View.OnAttachStateChangeListener { + private final int mMask1; + private final int mMask2; + private View mView; + private boolean mDisabled; + + public DisableStateTracker(int disableMask, int disable2Mask) { + mMask1 = disableMask; + mMask2 = disable2Mask; + } + + @Override + public void onViewAttachedToWindow(View v) { + mView = v; + SysUiServiceProvider.getComponent(v.getContext(), CommandQueue.class) + .addCallbacks(this); + } + + @Override + public void onViewDetachedFromWindow(View v) { + SysUiServiceProvider.getComponent(mView.getContext(), CommandQueue.class) + .removeCallbacks(this); + mView = null; + } + + /** + * Sets visibility of this {@link View} given the states passed from + * {@link com.android.systemui.statusbar.CommandQueue.Callbacks#disable(int, int)}. + */ + @Override + public void disable(int state1, int state2, boolean animate) { + final boolean disabled = ((state1 & mMask1) != 0) || ((state2 & mMask2) != 0); + if (disabled == mDisabled) return; + mDisabled = disabled; + mView.setVisibility(disabled ? View.GONE : View.VISIBLE); + } + + /** @return {@code true} if and only if this {@link View} is currently disabled */ + public boolean isDisabled() { + return mDisabled; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java index 49a12f401c90..2c85bb6e1644 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/Events.java +++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java @@ -51,6 +51,7 @@ public class Events { public static final int EVENT_SUPPRESSOR_CHANGED = 14; // (component|string) (name|string) public static final int EVENT_MUTE_CHANGED = 15; // (stream|int) (muted|bool) public static final int EVENT_TOUCH_LEVEL_DONE = 16; // (stream|int) (level|bool) + public static final int EVENT_ZEN_CONFIG_CHANGED = 17; // (allow/disallow|string) private static final String[] EVENT_TAGS = { "show_dialog", @@ -70,6 +71,7 @@ public class Events { "suppressor_changed", "mute_changed", "touch_level_done", + "zen_mode_config_changed", }; public static final int DISMISS_REASON_UNKNOWN = 0; @@ -80,6 +82,7 @@ public class Events { public static final int DISMISS_REASON_SETTINGS_CLICKED = 5; public static final int DISMISS_REASON_DONE_CLICKED = 6; public static final int DISMISS_STREAM_GONE = 7; + public static final int DISMISS_REASON_OUTPUT_CHOOSER = 8; public static final String[] DISMISS_REASONS = { "unknown", "touch_outside", @@ -88,7 +91,8 @@ public class Events { "screen_off", "settings_clicked", "done_clicked", - "a11y_stream_changed" + "a11y_stream_changed", + "output_chooser" }; public static final int SHOW_REASON_UNKNOWN = 0; diff --git a/packages/SystemUI/src/com/android/systemui/volume/MediaRouterWrapper.java b/packages/SystemUI/src/com/android/systemui/volume/MediaRouterWrapper.java new file mode 100644 index 000000000000..3423452c2a27 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/MediaRouterWrapper.java @@ -0,0 +1,51 @@ +/* + * 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.volume; + +import android.support.v7.media.MediaRouteSelector; +import android.support.v7.media.MediaRouter; + +import java.util.List; + +/** + * Wrapper for final class MediaRouter, for testing. + */ +public class MediaRouterWrapper { + + private final MediaRouter mRouter; + + public MediaRouterWrapper(MediaRouter router) + { + mRouter = router; + } + + public void addCallback(MediaRouteSelector selector, MediaRouter.Callback callback, int flags) { + mRouter.addCallback(selector, callback, flags); + } + + public void removeCallback(MediaRouter.Callback callback) { + mRouter.removeCallback(callback); + } + + public void unselect(int reason) { + mRouter.unselect(reason); + } + + public List<MediaRouter.RouteInfo> getRoutes() { + return mRouter.getRoutes(); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java new file mode 100644 index 000000000000..6ed07f8d2c37 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java @@ -0,0 +1,534 @@ +/* + * 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.volume; + +import static android.support.v7.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTED; +import static android.support.v7.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTING; +import static android.support.v7.media.MediaRouter.UNSELECT_REASON_DISCONNECTED; + +import static com.android.settingslib.bluetooth.Utils.getBtClassDrawableWithDescription; + +import android.app.Dialog; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.support.v7.media.MediaControlIntent; +import android.support.v7.media.MediaRouteSelector; +import android.support.v7.media.MediaRouter; +import android.telecom.TelecomManager; +import android.util.Log; +import android.util.Pair; +import android.view.Window; +import android.view.WindowManager; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; +import com.android.settingslib.Utils; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.systemui.Dependency; +import com.android.systemui.HardwareUiLayout; +import com.android.systemui.Interpolators; +import com.android.systemui.R; +import com.android.systemui.plugins.VolumeDialogController; +import com.android.systemui.statusbar.policy.BluetoothController; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; + +public class OutputChooserDialog extends Dialog + implements DialogInterface.OnDismissListener, OutputChooserLayout.Callback { + + private static final String TAG = Util.logTag(OutputChooserDialog.class); + private static final int MAX_DEVICES = 10; + + private static final long UPDATE_DELAY_MS = 300L; + private static final int MSG_UPDATE_ITEMS = 1; + + private final Context mContext; + private final BluetoothController mBluetoothController; + private WifiManager mWifiManager; + private OutputChooserLayout mView; + private final MediaRouterWrapper mRouter; + private final MediaRouterCallback mRouterCallback; + private long mLastUpdateTime; + static final boolean INCLUDE_MEDIA_ROUTES = false; + private boolean mIsInCall; + protected boolean isAttached; + + private final MediaRouteSelector mRouteSelector; + private Drawable mDefaultIcon; + private Drawable mTvIcon; + private Drawable mSpeakerIcon; + private Drawable mSpeakerGroupIcon; + private HardwareUiLayout mHardwareLayout; + private final VolumeDialogController mController; + + public OutputChooserDialog(Context context, MediaRouterWrapper router) { + super(context, com.android.systemui.R.style.qs_theme); + mContext = context; + mBluetoothController = Dependency.get(BluetoothController.class); + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + TelecomManager tm = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); + mIsInCall = tm.isInCall(); + mRouter = router; + mRouterCallback = new MediaRouterCallback(); + mRouteSelector = new MediaRouteSelector.Builder() + .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) + .build(); + + final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + context.registerReceiver(mReceiver, filter); + + mController = Dependency.get(VolumeDialogController.class); + + // Window initialization + Window window = getWindow(); + window.requestFeature(Window.FEATURE_NO_TITLE); + window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); + window.addFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); + window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); + } + + protected void setIsInCall(boolean inCall) { + mIsInCall = inCall; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.output_chooser); + setCanceledOnTouchOutside(true); + setOnDismissListener(this::onDismiss); + + mView = findViewById(R.id.output_chooser); + mHardwareLayout = HardwareUiLayout.get(mView); + mHardwareLayout.setOutsideTouchListener(view -> dismiss()); + mHardwareLayout.setSwapOrientation(false); + mView.setCallback(this); + + if (mIsInCall) { + mView.setTitle(R.string.output_calls_title); + } else { + mView.setTitle(R.string.output_title); + } + + mDefaultIcon = mContext.getDrawable(R.drawable.ic_cast); + mTvIcon = mContext.getDrawable(R.drawable.ic_tv); + mSpeakerIcon = mContext.getDrawable(R.drawable.ic_speaker); + mSpeakerGroupIcon = mContext.getDrawable(R.drawable.ic_speaker_group); + + final boolean wifiOff = !mWifiManager.isWifiEnabled(); + final boolean btOff = !mBluetoothController.isBluetoothEnabled(); + if (wifiOff && btOff) { + mView.setEmptyState(getDisabledServicesMessage(wifiOff, btOff)); + } + // time out after 5 seconds + mView.postDelayed(() -> updateItems(true), 5000); + } + + protected void cleanUp() {} + + + @Override + protected void onStart() { + super.onStart(); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (!mIsInCall && INCLUDE_MEDIA_ROUTES) { + mRouter.addCallback(mRouteSelector, mRouterCallback, + MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); + } + mBluetoothController.addCallback(mCallback); + mController.addCallback(mControllerCallbackH, mHandler); + isAttached = true; + } + + @Override + public void onDetachedFromWindow() { + isAttached = false; + mRouter.removeCallback(mRouterCallback); + mController.removeCallback(mControllerCallbackH); + mBluetoothController.removeCallback(mCallback); + super.onDetachedFromWindow(); + } + + @Override + public void onDismiss(DialogInterface unused) { + mContext.unregisterReceiver(mReceiver); + cleanUp(); + } + + @Override + public void show() { + super.show(); + Dependency.get(MetricsLogger.class).visible(MetricsProto.MetricsEvent.OUTPUT_CHOOSER); + mHardwareLayout.setTranslationX(getAnimTranslation()); + mHardwareLayout.setAlpha(0); + mHardwareLayout.animate() + .alpha(1) + .translationX(0) + .setDuration(300) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .withEndAction(() -> getWindow().getDecorView().requestAccessibilityFocus()) + .start(); + } + + @Override + public void dismiss() { + Dependency.get(MetricsLogger.class).hidden(MetricsProto.MetricsEvent.OUTPUT_CHOOSER); + mHardwareLayout.setTranslationX(0); + mHardwareLayout.setAlpha(1); + mHardwareLayout.animate() + .alpha(0) + .translationX(getAnimTranslation()) + .setDuration(300) + .withEndAction(() -> super.dismiss()) + .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator()) + .start(); + } + + private float getAnimTranslation() { + return getContext().getResources().getDimension( + com.android.systemui.R.dimen.output_chooser_panel_width) / 2; + } + + @Override + public void onDetailItemClick(OutputChooserLayout.Item item) { + if (item == null || item.tag == null) return; + if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) { + final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag; + if (device.getMaxConnectionState() == BluetoothProfile.STATE_DISCONNECTED) { + Dependency.get(MetricsLogger.class).action( + MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_CONNECT); + mBluetoothController.connect(device); + } + } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) { + final MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) item.tag; + if (route.isEnabled()) { + Dependency.get(MetricsLogger.class).action( + MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_CONNECT); + route.select(); + } + } + } + + @Override + public void onDetailItemDisconnect(OutputChooserLayout.Item item) { + if (item == null || item.tag == null) return; + if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) { + final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag; + Dependency.get(MetricsLogger.class).action( + MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_DISCONNECT); + mBluetoothController.disconnect(device); + } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) { + Dependency.get(MetricsLogger.class).action( + MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_DISCONNECT); + mRouter.unselect(UNSELECT_REASON_DISCONNECTED); + } + } + + private void updateItems(boolean timeout) { + if (SystemClock.uptimeMillis() - mLastUpdateTime < UPDATE_DELAY_MS) { + mHandler.removeMessages(MSG_UPDATE_ITEMS); + mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_UPDATE_ITEMS, timeout), + mLastUpdateTime + UPDATE_DELAY_MS); + return; + } + mLastUpdateTime = SystemClock.uptimeMillis(); + if (mView == null) return; + ArrayList<OutputChooserLayout.Item> items = new ArrayList<>(); + + // Add bluetooth devices + addBluetoothDevices(items); + + // Add remote displays + if (!mIsInCall && INCLUDE_MEDIA_ROUTES) { + addRemoteDisplayRoutes(items); + } + + items.sort(ItemComparator.sInstance); + + if (items.size() == 0 && timeout) { + String emptyMessage = mContext.getString(R.string.output_none_found); + final boolean wifiOff = !mWifiManager.isWifiEnabled(); + final boolean btOff = !mBluetoothController.isBluetoothEnabled(); + if (wifiOff || btOff) { + emptyMessage = getDisabledServicesMessage(wifiOff, btOff); + } + mView.setEmptyState(emptyMessage); + } + + mView.setItems(items.toArray(new OutputChooserLayout.Item[items.size()])); + } + + private String getDisabledServicesMessage(boolean wifiOff, boolean btOff) { + return mContext.getString(R.string.output_none_found_service_off, + wifiOff && btOff ? mContext.getString(R.string.output_service_bt_wifi) + : wifiOff ? mContext.getString(R.string.output_service_wifi) + : mContext.getString(R.string.output_service_bt)); + } + + private void addBluetoothDevices(List<OutputChooserLayout.Item> items) { + final Collection<CachedBluetoothDevice> devices = mBluetoothController.getDevices(); + if (devices != null) { + int connectedDevices = 0; + int count = 0; + for (CachedBluetoothDevice device : devices) { + if (mBluetoothController.getBondState(device) == BluetoothDevice.BOND_NONE) continue; + final int majorClass = device.getBtClass().getMajorDeviceClass(); + if (majorClass != BluetoothClass.Device.Major.AUDIO_VIDEO + && majorClass != BluetoothClass.Device.Major.UNCATEGORIZED) { + continue; + } + final OutputChooserLayout.Item item = new OutputChooserLayout.Item(); + item.iconResId = R.drawable.ic_qs_bluetooth_on; + item.line1 = device.getName(); + item.tag = device; + item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_BT; + int state = device.getMaxConnectionState(); + if (state == BluetoothProfile.STATE_CONNECTED) { + item.iconResId = R.drawable.ic_qs_bluetooth_connected; + int batteryLevel = device.getBatteryLevel(); + if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { + Pair<Drawable, String> pair = + getBtClassDrawableWithDescription(getContext(), device); + item.icon = pair.first; + item.line2 = mContext.getString( + R.string.quick_settings_connected_battery_level, + Utils.formatPercentage(batteryLevel)); + } else { + item.line2 = mContext.getString(R.string.quick_settings_connected); + } + item.canDisconnect = true; + items.add(connectedDevices, item); + connectedDevices++; + } else if (state == BluetoothProfile.STATE_CONNECTING) { + item.iconResId = R.drawable.ic_qs_bluetooth_connecting; + item.line2 = mContext.getString(R.string.quick_settings_connecting); + items.add(connectedDevices, item); + } else { + items.add(item); + } + if (++count == MAX_DEVICES) { + break; + } + } + } + } + + private void addRemoteDisplayRoutes(List<OutputChooserLayout.Item> items) { + List<MediaRouter.RouteInfo> routes = mRouter.getRoutes(); + for(MediaRouter.RouteInfo route : routes) { + if (route.isDefaultOrBluetooth() || !route.isEnabled() + || !route.matchesSelector(mRouteSelector)) { + continue; + } + final OutputChooserLayout.Item item = new OutputChooserLayout.Item(); + item.icon = getIconDrawable(route); + item.line1 = route.getName(); + item.tag = route; + item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER; + if (route.getConnectionState() == CONNECTION_STATE_CONNECTING) { + mContext.getString(R.string.quick_settings_connecting); + } else { + item.line2 = route.getDescription(); + } + + if (route.getConnectionState() == CONNECTION_STATE_CONNECTED) { + item.canDisconnect = true; + } + items.add(item); + } + } + + private Drawable getIconDrawable(MediaRouter.RouteInfo route) { + Uri iconUri = route.getIconUri(); + if (iconUri != null) { + try { + InputStream is = getContext().getContentResolver().openInputStream(iconUri); + Drawable drawable = Drawable.createFromStream(is, null); + if (drawable != null) { + return drawable; + } + } catch (IOException e) { + Log.w(TAG, "Failed to load " + iconUri, e); + // Falls back. + } + } + return getDefaultIconDrawable(route); + } + + private Drawable getDefaultIconDrawable(MediaRouter.RouteInfo route) { + // If the type of the receiver device is specified, use it. + switch (route.getDeviceType()) { + case MediaRouter.RouteInfo.DEVICE_TYPE_TV: + return mTvIcon; + case MediaRouter.RouteInfo.DEVICE_TYPE_SPEAKER: + return mSpeakerIcon; + } + + // Otherwise, make the best guess based on other route information. + if (route instanceof MediaRouter.RouteGroup) { + // Only speakers can be grouped for now. + return mSpeakerGroupIcon; + } + return mDefaultIcon; + } + + private final class MediaRouterCallback extends MediaRouter.Callback { + @Override + public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) { + updateItems(false); + } + + @Override + public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) { + updateItems(false); + } + + @Override + public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) { + updateItems(false); + } + + @Override + public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) { + updateItems(false); + } + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { + if (D.BUG) Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS"); + cancel(); + cleanUp(); + } + } + }; + + private final BluetoothController.Callback mCallback = new BluetoothController.Callback() { + @Override + public void onBluetoothStateChange(boolean enabled) { + updateItems(false); + } + + @Override + public void onBluetoothDevicesChanged() { + updateItems(false); + } + }; + + static final class ItemComparator implements Comparator<OutputChooserLayout.Item> { + public static final ItemComparator sInstance = new ItemComparator(); + + @Override + public int compare(OutputChooserLayout.Item lhs, OutputChooserLayout.Item rhs) { + // Connected item(s) first + if (lhs.canDisconnect != rhs.canDisconnect) { + return Boolean.compare(rhs.canDisconnect, lhs.canDisconnect); + } + // Bluetooth items before media routes + if (lhs.deviceType != rhs.deviceType) { + return Integer.compare(lhs.deviceType, rhs.deviceType); + } + // then by name + return lhs.line1.toString().compareToIgnoreCase(rhs.line1.toString()); + } + } + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MSG_UPDATE_ITEMS: + updateItems((Boolean) message.obj); + break; + } + } + }; + + private final VolumeDialogController.Callbacks mControllerCallbackH + = new VolumeDialogController.Callbacks() { + @Override + public void onShowRequested(int reason) { + dismiss(); + } + + @Override + public void onDismissRequested(int reason) {} + + @Override + public void onScreenOff() { + dismiss(); + } + + @Override + public void onStateChanged(VolumeDialogController.State state) {} + + @Override + public void onLayoutDirectionChanged(int layoutDirection) {} + + @Override + public void onConfigurationChanged() {} + + @Override + public void onShowVibrateHint() {} + + @Override + public void onShowSilentHint() {} + + @Override + public void onShowSafetyWarning(int flags) {} + + @Override + public void onAccessibilityModeChanged(Boolean showA11yStream) {} + + @Override + public void onConnectedDeviceChanged(String deviceName) {} + }; +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java new file mode 100644 index 000000000000..d4c6f897846e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java @@ -0,0 +1,256 @@ +/* + * 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.volume; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.systemui.FontSizeUtils; +import com.android.systemui.R; +import com.android.systemui.qs.AutoSizingList; + +/** + * Limited height list of devices. + */ +public class OutputChooserLayout extends LinearLayout { + private static final String TAG = "OutputChooserLayout"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final Context mContext; + private final H mHandler = new H(); + private final Adapter mAdapter = new Adapter(); + + private String mTag; + private Callback mCallback; + private boolean mItemsVisible = true; + private AutoSizingList mItemList; + private View mEmpty; + private TextView mEmptyText; + private TextView mTitle; + + private Item[] mItems; + + public OutputChooserLayout(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + mTag = TAG; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mItemList = findViewById(android.R.id.list); + mItemList.setVisibility(GONE); + mItemList.setAdapter(mAdapter); + mEmpty = findViewById(android.R.id.empty); + mEmpty.setVisibility(GONE); + mEmptyText = mEmpty.findViewById(R.id.empty_text); + mTitle = findViewById(R.id.title); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + FontSizeUtils.updateFontSize(mEmptyText, R.dimen.qs_detail_empty_text_size); + int count = mItemList.getChildCount(); + for (int i = 0; i < count; i++) { + View item = mItemList.getChildAt(i); + FontSizeUtils.updateFontSize(item, R.id.empty_text, + R.dimen.qs_detail_item_primary_text_size); + FontSizeUtils.updateFontSize(item, android.R.id.summary, + R.dimen.qs_detail_item_secondary_text_size); + FontSizeUtils.updateFontSize(item, android.R.id.title, + R.dimen.qs_detail_header_text_size); + } + } + + public void setTitle(int title) { + mTitle.setText(title); + } + + public void setEmptyState(String text) { + mEmptyText.setText(text); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (DEBUG) Log.d(mTag, "onAttachedToWindow"); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (DEBUG) Log.d(mTag, "onDetachedFromWindow"); + mCallback = null; + } + + public void setCallback(Callback callback) { + mHandler.removeMessages(H.SET_CALLBACK); + mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget(); + } + + public void setItems(Item[] items) { + mHandler.removeMessages(H.SET_ITEMS); + mHandler.obtainMessage(H.SET_ITEMS, items).sendToTarget(); + } + + public void setItemsVisible(boolean visible) { + mHandler.removeMessages(H.SET_ITEMS_VISIBLE); + mHandler.obtainMessage(H.SET_ITEMS_VISIBLE, visible ? 1 : 0, 0).sendToTarget(); + } + + private void handleSetCallback(Callback callback) { + mCallback = callback; + } + + private void handleSetItems(Item[] items) { + final int itemCount = items != null ? items.length : 0; + mEmpty.setVisibility(itemCount == 0 ? VISIBLE : GONE); + mItemList.setVisibility(itemCount == 0 ? GONE : VISIBLE); + mItems = items; + mAdapter.notifyDataSetChanged(); + } + + private void handleSetItemsVisible(boolean visible) { + if (mItemsVisible == visible) return; + mItemsVisible = visible; + for (int i = 0; i < mItemList.getChildCount(); i++) { + mItemList.getChildAt(i).setVisibility(mItemsVisible ? VISIBLE : INVISIBLE); + } + } + + private class Adapter extends BaseAdapter { + + @Override + public int getCount() { + return mItems != null ? mItems.length : 0; + } + + @Override + public Object getItem(int position) { + return mItems[position]; + } + + @Override + public long getItemId(int position) { + return 0; + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + final Item item = mItems[position]; + if (view == null) { + view = LayoutInflater.from(mContext).inflate(R.layout.output_chooser_item, parent, + false); + } + view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE); + final ImageView iv = view.findViewById(android.R.id.icon); + if (item.icon != null) { + iv.setImageDrawable(item.icon); + } else { + iv.setImageResource(item.iconResId); + } + final TextView title = view.findViewById(android.R.id.title); + title.setText(item.line1); + final TextView summary = view.findViewById(android.R.id.summary); + final boolean twoLines = !TextUtils.isEmpty(item.line2); + title.setMaxLines(twoLines ? 1 : 2); + summary.setVisibility(twoLines ? VISIBLE : GONE); + summary.setText(twoLines ? item.line2 : null); + view.setOnClickListener(v -> { + if (mCallback != null) { + mCallback.onDetailItemClick(item); + } + }); + + final ImageView icon2 = view.findViewById(android.R.id.icon2); + if (item.canDisconnect) { + icon2.setImageResource(R.drawable.ic_qs_cancel); + icon2.setVisibility(VISIBLE); + icon2.setClickable(true); + icon2.setOnClickListener(v -> { + if (mCallback != null) { + mCallback.onDetailItemDisconnect(item); + } + }); + } else if (item.icon2 != -1) { + icon2.setVisibility(VISIBLE); + icon2.setImageResource(item.icon2); + icon2.setClickable(false); + } else { + icon2.setVisibility(GONE); + } + + return view; + } + }; + + private class H extends Handler { + private static final int SET_ITEMS = 1; + private static final int SET_CALLBACK = 2; + private static final int SET_ITEMS_VISIBLE = 3; + + public H() { + super(Looper.getMainLooper()); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == SET_ITEMS) { + handleSetItems((Item[]) msg.obj); + } else if (msg.what == SET_CALLBACK) { + handleSetCallback((OutputChooserLayout.Callback) msg.obj); + } else if (msg.what == SET_ITEMS_VISIBLE) { + handleSetItemsVisible(msg.arg1 != 0); + } + } + } + + public static class Item { + public static int DEVICE_TYPE_BT = 1; + public static int DEVICE_TYPE_MEDIA_ROUTER = 2; + public int iconResId; + public Drawable icon; + public Drawable overlay; + public CharSequence line1; + public CharSequence line2; + public Object tag; + public boolean canDisconnect; + public int icon2 = -1; + public int deviceType = 0; + } + + public interface Callback { + void onDetailItemClick(Item item); + void onDetailItemDisconnect(Item item); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/SystemUIInterpolators.java b/packages/SystemUI/src/com/android/systemui/volume/SystemUIInterpolators.java new file mode 100644 index 000000000000..5ad8840a9dd1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/SystemUIInterpolators.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import android.animation.TimeInterpolator; + +public class SystemUIInterpolators { + public static final class LogDecelerateInterpolator implements TimeInterpolator { + private final float mBase; + private final float mDrift; + private final float mTimeScale; + private final float mOutputScale; + + public LogDecelerateInterpolator() { + this(400f, 1.4f, 0); + } + + private LogDecelerateInterpolator(float base, float timeScale, float drift) { + mBase = base; + mDrift = drift; + mTimeScale = 1f / timeScale; + + mOutputScale = 1f / computeLog(1f); + } + + private float computeLog(float t) { + return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t); + } + + @Override + public float getInterpolation(float t) { + return computeLog(t) * mOutputScale; + } + } + + public static final class LogAccelerateInterpolator implements TimeInterpolator { + private final int mBase; + private final int mDrift; + private final float mLogScale; + + public LogAccelerateInterpolator() { + this(100, 0); + } + + private LogAccelerateInterpolator(int base, int drift) { + mBase = base; + mDrift = drift; + mLogScale = 1f / computeLog(1, mBase, mDrift); + } + + private static float computeLog(float t, int base, int drift) { + return (float) -Math.pow(base, -t) + 1 + (drift * t); + } + + @Override + public float getInterpolation(float t) { + return 1 - computeLog(1 - t, mBase, mDrift) * mLogScale; + } + } + + public interface Callback { + void onAnimatingChanged(boolean animating); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java index f6d36e855964..0203c43d3683 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java @@ -27,16 +27,15 @@ import android.os.Handler; import android.view.WindowManager.LayoutParams; import com.android.settingslib.applications.InterestingConfigChanges; -import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.Dependency; import com.android.systemui.SystemUI; import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.VolumeDialog; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.statusbar.policy.ExtensionController; -import com.android.systemui.statusbar.policy.ExtensionController.Extension; import com.android.systemui.tuner.TunerService; import java.io.FileDescriptor; @@ -52,9 +51,9 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna public static final String VOLUME_UP_SILENT = "sysui_volume_up_silent"; public static final String VOLUME_SILENT_DO_NOT_DISTURB = "sysui_do_not_disturb"; - public static final boolean DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT = true; - public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = true; - public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = true; + public static final boolean DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT = false; + public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = false; + public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = false; private final SystemUI mSysui; private final Context mContext; @@ -62,7 +61,6 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_ASSETS_PATHS); - private final Extension mExtension; private VolumeDialog mDialog; private VolumePolicy mVolumePolicy = new VolumePolicy( DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT, // volumeDownToEnterSilent @@ -79,7 +77,7 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna // Allow plugins to reference the VolumeDialogController. Dependency.get(PluginDependencyProvider.class) .allowPluginDependency(VolumeDialogController.class); - mExtension = Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class) + Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class) .withPlugin(VolumeDialog.class) .withDefault(this::createDefault) .withCallback(dialog -> { @@ -96,7 +94,6 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna private VolumeDialog createDefault() { VolumeDialogImpl impl = new VolumeDialogImpl(mContext); - impl.setStreamImportant(AudioManager.STREAM_ALARM, true); impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); impl.setAutomute(true); impl.setSilentMode(false); @@ -136,6 +133,10 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna mController.setVolumePolicy(mVolumePolicy); } + void setEnableDialogs(boolean volumeUi, boolean safetyWarning) { + mController.setEnableDialogs(volumeUi, safetyWarning); + } + @Override public void onUserActivity() { final KeyguardViewMediator kvm = mSysui.getComponent(KeyguardViewMediator.class); @@ -152,7 +153,7 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna @Override public void onConfigurationChanged(Configuration newConfig) { if (mConfigChanges.applyNewConfig(mContext.getResources())) { - mExtension.reload(); + mController.mCallbacks.onConfigurationChanged(); } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index b08b26d77b76..3c29b7740102 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -26,6 +26,8 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.database.ContentObserver; +import android.media.AudioDeviceCallback; +import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.AudioSystem; import android.media.IVolumeController; @@ -41,6 +43,7 @@ import android.os.RemoteException; import android.os.Vibrator; import android.provider.Settings; import android.service.notification.Condition; +import android.service.notification.ZenModeConfig; import android.util.ArrayMap; import android.util.Log; import android.view.accessibility.AccessibilityManager; @@ -49,7 +52,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.SysUiServiceProvider; -import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.qs.tiles.DndTile; @@ -57,7 +59,9 @@ import com.android.systemui.statusbar.phone.StatusBar; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -74,7 +78,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private static final int DYNAMIC_STREAM_START_INDEX = 100; private static final int VIBRATE_HINT_DURATION = 50; - private static final ArrayMap<Integer, Integer> STREAMS = new ArrayMap<>(); + static final ArrayMap<Integer, Integer> STREAMS = new ArrayMap<>(); static { STREAMS.put(AudioSystem.STREAM_ALARM, R.string.stream_alarm); STREAMS.put(AudioSystem.STREAM_BLUETOOTH_SCO, R.string.stream_bluetooth_sco); @@ -104,6 +108,13 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private final Vibrator mVibrator; private final boolean mHasVibrator; private boolean mShowA11yStream; + private boolean mShowVolumeDialog; + private boolean mShowSafetyWarning; + private DeviceCallback mDeviceCallback = new DeviceCallback(); + private final NotificationManager mNotificationManager; + @GuardedBy("mLock") + private List<AudioDeviceInfo> mConnectedDevices = new ArrayList<>(); + private Object mLock = new Object(); private boolean mDestroyed; private VolumePolicy mVolumePolicy; @@ -115,6 +126,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa public VolumeDialogControllerImpl(Context context) { mContext = context.getApplicationContext(); + mNotificationManager = (NotificationManager) mContext.getSystemService( + Context.NOTIFICATION_SERVICE); Events.writeEvent(mContext, Events.EVENT_COLLECTION_STARTED); mWorkerThread = new HandlerThread(VolumeDialogControllerImpl.class.getSimpleName()); mWorkerThread.start(); @@ -167,7 +180,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } protected int getAudioManagerStreamMinVolume(int stream) { - return mAudio.getStreamMinVolume(stream); + return mAudio.getStreamMinVolumeInt(stream); } public void register() { @@ -179,6 +192,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } catch (SecurityException e) { Log.w(TAG, "No access to media sessions", e); } + mAudio.registerAudioDeviceCallback(mDeviceCallback, mWorker); } public void setVolumePolicy(VolumePolicy policy) { @@ -204,6 +218,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa mMediaSessions.destroy(); mObserver.destroy(); mReceiver.destroy(); + mAudio.unregisterAudioDeviceCallback(mDeviceCallback); mWorkerThread.quitSafely(); } @@ -283,6 +298,11 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa mWorker.obtainMessage(W.SET_ACTIVE_STREAM, stream, 0).sendToTarget(); } + public void setEnableDialogs(boolean volumeUi, boolean safetyWarning) { + mShowVolumeDialog = volumeUi; + mShowSafetyWarning = safetyWarning; + } + public void vibrate() { if (mHasVibrator) { mVibrator.vibrate(VIBRATE_HINT_DURATION); @@ -312,7 +332,9 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } private void onShowSafetyWarningW(int flags) { - mCallbacks.onShowSafetyWarning(flags); + if (mShowSafetyWarning) { + mCallbacks.onShowSafetyWarning(flags); + } } private void onAccessibilityModeChanged(Boolean showA11yStream) { @@ -340,11 +362,15 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private boolean shouldShowUI(int flags) { updateStatusBar(); - return mStatusBar != null - && mStatusBar.getWakefulnessState() != WakefulnessLifecycle.WAKEFULNESS_ASLEEP - && mStatusBar.getWakefulnessState() != WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP + // if status bar isn't null, check if phone is in AOD, else check flags + // since we could be using a different status bar + return mStatusBar != null ? + mStatusBar.getWakefulnessState() != WakefulnessLifecycle.WAKEFULNESS_ASLEEP + && mStatusBar.getWakefulnessState() != + WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP && mStatusBar.isDeviceInteractive() - && (flags & AudioManager.FLAG_SHOW_UI) != 0; + && (flags & AudioManager.FLAG_SHOW_UI) != 0 && mShowVolumeDialog + : mShowVolumeDialog && (flags & AudioManager.FLAG_SHOW_UI) != 0; } boolean onVolumeChangedW(int stream, int flags) { @@ -410,6 +436,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } updateRingerModeExternalW(mAudio.getRingerMode()); updateZenModeW(); + updateZenConfig(); updateEffectsSuppressorW(mNoMan.getEffectsSuppressor()); mCallbacks.onStateChanged(mState); } @@ -495,6 +522,26 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa return true; } + private boolean updateZenConfig() { + final NotificationManager.Policy policy = mNotificationManager.getNotificationPolicy(); + boolean disallowAlarms = (policy.priorityCategories & NotificationManager.Policy + .PRIORITY_CATEGORY_ALARMS) == 0; + boolean disallowMedia = (policy.priorityCategories & NotificationManager.Policy + .PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0; + boolean disallowRinger = ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(policy); + if (mState.disallowAlarms == disallowAlarms && mState.disallowMedia == disallowMedia + && mState.disallowRinger == disallowRinger) { + return false; + } + mState.disallowAlarms = disallowAlarms; + mState.disallowMedia = disallowMedia; + mState.disallowRinger = disallowRinger; + Events.writeEvent(mContext, Events.EVENT_ZEN_CONFIG_CHANGED, "disallowAlarms=" + + disallowAlarms + " disallowMedia=" + disallowMedia + " disallowRinger=" + + disallowRinger); + return true; + } + private boolean updateRingerModeExternalW(int rm) { if (rm == mState.ringerModeExternal) return false; mState.ringerModeExternal = rm; @@ -655,6 +702,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa case USER_ACTIVITY: onUserActivityW(); break; case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break; case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj); + } } } @@ -794,6 +842,18 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa }); } } + + @Override + public void onConnectedDeviceChanged(String deviceName) { + for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { + entry.getValue().post(new Runnable() { + @Override + public void run() { + entry.getKey().onConnectedDeviceChanged(deviceName); + } + }); + } + } } @@ -822,6 +882,10 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa if (ZEN_MODE_URI.equals(uri)) { changed = updateZenModeW(); } + if (ZEN_MODE_CONFIG_URI.equals(uri)) { + changed |= updateZenConfig(); + } + if (changed) { mCallbacks.onStateChanged(mState); } @@ -996,6 +1060,33 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } } + protected final class DeviceCallback extends AudioDeviceCallback { + public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { + synchronized (mLock) { + for (AudioDeviceInfo info : addedDevices) { + if (info.isSink() + && (info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP + || info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO)) { + mConnectedDevices.add(info); + mCallbacks.onConnectedDeviceChanged(info.getProductName().toString()); + } + } + } + } + + public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { + synchronized (mLock) { + for (AudioDeviceInfo info : removedDevices) { + mConnectedDevices.remove(info); + } + + if (mConnectedDevices.size() == 0) { + mCallbacks.onConnectedDeviceChanged(null); + } + } + } + } + public interface UserActivityListener { void onUserActivity(); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 41a5dc84b713..1e8e98ca2e42 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -18,6 +18,11 @@ package com.android.systemui.volume; import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK; import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC; +import static android.media.AudioManager.STREAM_ACCESSIBILITY; + +import static com.android.systemui.volume.Events.DISMISS_REASON_OUTPUT_CHOOSER; +import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; +import static com.android.systemui.volume.Events.DISMISS_REASON_TOUCH_OUTSIDE; import android.accessibilityservice.AccessibilityServiceInfo; import android.animation.ObjectAnimator; @@ -26,16 +31,14 @@ import android.annotation.SuppressLint; import android.app.Dialog; import android.app.KeyguardManager; import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.ColorStateList; -import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.media.AudioSystem; import android.os.Debug; @@ -43,11 +46,11 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; +import android.provider.Settings; import android.provider.Settings.Global; -import android.transition.AutoTransition; -import android.transition.Transition; -import android.transition.TransitionManager; -import android.util.DisplayMetrics; +import android.support.v7.media.MediaRouter; +import android.text.InputFilter; +import android.text.TextUtils; import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; @@ -58,32 +61,27 @@ import android.view.View; import android.view.View.AccessibilityDelegate; import android.view.View.OnAttachStateChangeListener; import android.view.View.OnClickListener; -import android.view.View.OnTouchListener; import android.view.ViewGroup; -import android.view.ViewGroup.MarginLayoutParams; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; import android.view.animation.DecelerateInterpolator; import android.widget.ImageButton; +import android.widget.ImageView; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; import com.android.settingslib.Utils; import com.android.systemui.Dependency; -import com.android.systemui.Interpolators; -import com.android.systemui.Prefs; import com.android.systemui.R; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.VolumeDialog; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.plugins.VolumeDialogController.StreamState; -import com.android.systemui.statusbar.policy.ZenModeController; -import com.android.systemui.tuner.TunerService; -import com.android.systemui.tuner.TunerZenModePanel; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import java.io.PrintWriter; import java.util.ArrayList; @@ -96,11 +94,9 @@ import java.util.List; * * Methods ending in "H" must be called on the (ui) handler. */ -public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { +public class VolumeDialogImpl implements VolumeDialog { private static final String TAG = Util.logTag(VolumeDialogImpl.class); - public static final String SHOW_FULL_ZEN = "sysui_show_full_zen"; - private static final long USER_ATTEMPT_GRACE_PERIOD = 1000; private static final int UPDATE_ANIMATION_DURATION = 80; @@ -112,26 +108,23 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { private CustomDialog mDialog; private ViewGroup mDialogView; private ViewGroup mDialogRowsView; - private ViewGroup mDialogContentView; - private ImageButton mExpandButton; + private ViewGroup mFooter; + private ImageButton mRingerIcon; + private ImageView mZenIcon; + private TextView mRingerStatus; + private TextView mRingerTitle; private final List<VolumeRow> mRows = new ArrayList<>(); private ConfigurableTexts mConfigurableTexts; private final SparseBooleanArray mDynamic = new SparseBooleanArray(); private final KeyguardManager mKeyguard; - private final AudioManager mAudioManager; - private final AccessibilityManager mAccessibilityMgr; - private int mExpandButtonAnimationDuration; - private ZenFooter mZenFooter; + private final AccessibilityManagerWrapper mAccessibilityMgr; private final Object mSafetyWarningLock = new Object(); + private final Object mOutputChooserLock = new Object(); private final Accessibility mAccessibility = new Accessibility(); private final ColorStateList mActiveSliderTint; private final ColorStateList mInactiveSliderTint; - private VolumeDialogMotion mMotion; - private int mWindowType; - private final ZenModeController mZenModeController; private boolean mShowing; - private boolean mExpanded; private boolean mShowA11yStream; private int mActiveStream; @@ -139,54 +132,32 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE; private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE; private State mState; - private boolean mExpandButtonAnimationRunning; private SafetyWarningDialog mSafetyWarning; - private Callback mCallback; - private boolean mPendingStateChanged; - private boolean mPendingRecheckAll; - private long mCollapseTime; + private OutputChooserDialog mOutputChooserDialog; private boolean mHovering = false; - private int mDensity; - - private boolean mShowFullZen; - private TunerZenModePanel mZenPanel; public VolumeDialogImpl(Context context) { mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); - mZenModeController = Dependency.get(ZenModeController.class); mController = Dependency.get(VolumeDialogController.class); mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); - mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - mAccessibilityMgr = - (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); + mAccessibilityMgr = Dependency.get(AccessibilityManagerWrapper.class); mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext)); mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive); } public void init(int windowType, Callback callback) { - mCallback = callback; - mWindowType = windowType; - initDialog(); mAccessibility.init(); mController.addCallback(mControllerCallbackH, mHandler); mController.getState(); - Dependency.get(TunerService.class).addTunable(this, SHOW_FULL_ZEN); - - final Configuration currentConfig = mContext.getResources().getConfiguration(); - mDensity = currentConfig.densityDpi; } @Override public void destroy() { mAccessibility.destroy(); mController.removeCallback(mControllerCallbackH); - if (mZenFooter != null) { - mZenFooter.cleanup(); - } - Dependency.get(TunerService.class).removeTunable(this); mHandler.removeCallbacksAndMessages(null); } @@ -199,119 +170,90 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { mWindow = mDialog.getWindow(); mWindow.requestFeature(Window.FEATURE_NO_TITLE); mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); - mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); - mDialog.setCanceledOnTouchOutside(true); - final Resources res = mContext.getResources(); + mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); + mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast); final WindowManager.LayoutParams lp = mWindow.getAttributes(); - lp.type = mWindowType; lp.format = PixelFormat.TRANSLUCENT; lp.setTitle(VolumeDialogImpl.class.getSimpleName()); - lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; - lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top); - lp.gravity = Gravity.TOP; + lp.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL; lp.windowAnimations = -1; mWindow.setAttributes(lp); - mWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); + mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + mDialog.setCanceledOnTouchOutside(true); mDialog.setContentView(R.layout.volume_dialog); - mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog); - mDialogView.setOnHoverListener(new View.OnHoverListener() { - @Override - public boolean onHover(View v, MotionEvent event) { - int action = event.getActionMasked(); - mHovering = (action == MotionEvent.ACTION_HOVER_ENTER) - || (action == MotionEvent.ACTION_HOVER_MOVE); - rescheduleTimeoutH(); - return true; - } + mDialog.setOnShowListener(dialog -> { + mDialogView.setTranslationX(mDialogView.getWidth() / 2); + mDialogView.setAlpha(0); + mDialogView.animate() + .alpha(1) + .translationX(0) + .setDuration(300) + .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator()) + .withEndAction(() -> { + mWindow.getDecorView().requestAccessibilityFocus(); + }) + .start(); }); + mDialogView = mDialog.findViewById(R.id.volume_dialog); + mDialogView.setOnHoverListener((v, event) -> { + int action = event.getActionMasked(); + mHovering = (action == MotionEvent.ACTION_HOVER_ENTER) + || (action == MotionEvent.ACTION_HOVER_MOVE); + rescheduleTimeoutH(); + return true; + }); + VolumeUiLayout uiLayout = VolumeUiLayout.get(mDialogView); + uiLayout.updateRotation(); - mDialogContentView = mDialog.findViewById(R.id.volume_dialog_content); - mDialogRowsView = mDialogContentView.findViewById(R.id.volume_dialog_rows); - mExpanded = false; - mExpandButton = (ImageButton) mDialogView.findViewById(R.id.volume_expand_button); - mExpandButton.setOnClickListener(mClickExpand); - - mExpandButton.setVisibility( - AudioSystem.isSingleVolume(mContext) ? View.GONE : View.VISIBLE); - updateWindowWidthH(); - updateExpandButtonH(); - - mMotion = new VolumeDialogMotion(mDialog, mDialogView, mDialogContentView, mExpandButton, - new VolumeDialogMotion.Callback() { - @Override - public void onAnimatingChanged(boolean animating) { - if (animating) return; - if (mPendingStateChanged) { - mHandler.sendEmptyMessage(H.STATE_CHANGED); - mPendingStateChanged = false; - } - if (mPendingRecheckAll) { - mHandler.sendEmptyMessage(H.RECHECK_ALL); - mPendingRecheckAll = false; - } - } - }); + mDialogRowsView = mDialog.findViewById(R.id.volume_dialog_rows); + mFooter = mDialog.findViewById(R.id.footer); + mRingerIcon = mFooter.findViewById(R.id.ringer_icon); + mRingerStatus = mFooter.findViewById(R.id.ringer_status); + mRingerTitle = mFooter.findViewById(R.id.ringer_title); + mZenIcon = mFooter.findViewById(R.id.dnd_icon); if (mRows.isEmpty()) { addRow(AudioManager.STREAM_MUSIC, - R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true); + R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true); if (!AudioSystem.isSingleVolume(mContext)) { addRow(AudioManager.STREAM_RING, - R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true); + R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true, false); addRow(AudioManager.STREAM_ALARM, - R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, false); + R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, true, false); addRow(AudioManager.STREAM_VOICE_CALL, - R.drawable.ic_volume_voice, R.drawable.ic_volume_voice, false); + R.drawable.ic_volume_voice, R.drawable.ic_volume_voice, false, false); addRow(AudioManager.STREAM_BLUETOOTH_SCO, - R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false); - addRow(AudioManager.STREAM_SYSTEM, - R.drawable.ic_volume_system, R.drawable.ic_volume_system_mute, false); - addRow(AudioManager.STREAM_ACCESSIBILITY, R.drawable.ic_volume_accessibility, - R.drawable.ic_volume_accessibility, true); + R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false, false); + addRow(AudioManager.STREAM_SYSTEM, R.drawable.ic_volume_system, + R.drawable.ic_volume_system_mute, false, false); + addRow(STREAM_ACCESSIBILITY, R.drawable.ic_volume_accessibility, + R.drawable.ic_volume_accessibility, true, false); } } else { addExistingRows(); } - mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration); - mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer); - mZenFooter.init(mZenModeController); - mZenPanel = (TunerZenModePanel) mDialog.findViewById(R.id.tuner_zen_mode_panel); - mZenPanel.init(mZenModeController); - mZenPanel.setCallback(mZenPanelCallback); + + updateRowsH(getActiveRow()); + initRingerH(); } - @Override - public void onTuningChanged(String key, String newValue) { - if (SHOW_FULL_ZEN.equals(key)) { - mShowFullZen = newValue != null && Integer.parseInt(newValue) != 0; - } + protected ViewGroup getDialogView() { + return mDialogView; } private ColorStateList loadColorStateList(int colorResId) { return ColorStateList.valueOf(mContext.getColor(colorResId)); } - private void updateWindowWidthH() { - final ViewGroup.LayoutParams lp = mDialogView.getLayoutParams(); - final DisplayMetrics dm = mContext.getResources().getDisplayMetrics(); - if (D.BUG) Log.d(TAG, "updateWindowWidth dm.w=" + dm.widthPixels); - int w = dm.widthPixels; - final int max = mContext.getResources() - .getDimensionPixelSize(R.dimen.volume_dialog_panel_width); - if (w > max) { - w = max; - } - lp.width = w; - mDialogView.setLayoutParams(lp); - } - public void setStreamImportant(int stream, boolean important) { mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget(); } @@ -328,20 +270,20 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { mHandler.sendEmptyMessage(H.RECHECK_ALL); } - private void addRow(int stream, int iconRes, int iconMuteRes, boolean important) { - addRow(stream, iconRes, iconMuteRes, important, false); + private void addRow(int stream, int iconRes, int iconMuteRes, boolean important, + boolean defaultStream) { + addRow(stream, iconRes, iconMuteRes, important, defaultStream, false); } private void addRow(int stream, int iconRes, int iconMuteRes, boolean important, - boolean dynamic) { + boolean defaultStream, boolean dynamic) { + if (D.BUG) Slog.d(TAG, "Adding row for stream " + stream); VolumeRow row = new VolumeRow(); - initRow(row, stream, iconRes, iconMuteRes, important); + initRow(row, stream, iconRes, iconMuteRes, important, defaultStream); int rowSize; - int viewSize; - if (mShowA11yStream && dynamic && (rowSize = mRows.size()) > 1 - && (viewSize = mDialogRowsView.getChildCount()) > 1) { - // A11y Stream should be the last in the list - mDialogRowsView.addView(row.view, viewSize - 2); + if (mShowA11yStream && dynamic && (rowSize = mRows.size()) > 1) { + // A11y Stream should be the first in the list, so it's shown to start of other rows + mDialogRowsView.addView(row.view, 0); mRows.add(rowSize - 2, row); } else { mDialogRowsView.addView(row.view); @@ -353,16 +295,13 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { int N = mRows.size(); for (int i = 0; i < N; i++) { final VolumeRow row = mRows.get(i); - initRow(row, row.stream, row.iconRes, row.iconMuteRes, row.important); + initRow(row, row.stream, row.iconRes, row.iconMuteRes, row.important, + row.defaultStream); mDialogRowsView.addView(row.view); + updateVolumeRowH(row); } } - - private boolean isAttached() { - return mDialogContentView != null && mDialogContentView.isAttachedToWindow(); - } - private VolumeRow getActiveRow() { for (VolumeRow row : mRows) { if (row.stream == mActiveStream) { @@ -382,14 +321,10 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { public void dump(PrintWriter writer) { writer.println(VolumeDialogImpl.class.getSimpleName() + " state:"); writer.print(" mShowing: "); writer.println(mShowing); - writer.print(" mExpanded: "); writer.println(mExpanded); - writer.print(" mExpandButtonAnimationRunning: "); - writer.println(mExpandButtonAnimationRunning); writer.print(" mActiveStream: "); writer.println(mActiveStream); writer.print(" mDynamic: "); writer.println(mDynamic); writer.print(" mAutomute: "); writer.println(mAutomute); writer.print(" mSilentMode: "); writer.println(mSilentMode); - writer.print(" mCollapseTime: "); writer.println(mCollapseTime); writer.print(" mAccessibility.mFeedbackEnabled: "); writer.println(mAccessibility.mFeedbackEnabled); } @@ -404,82 +339,101 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { @SuppressLint("InflateParams") private void initRow(final VolumeRow row, final int stream, int iconRes, int iconMuteRes, - boolean important) { + boolean important, boolean defaultStream) { row.stream = stream; row.iconRes = iconRes; row.iconMuteRes = iconMuteRes; row.important = important; + row.defaultStream = defaultStream; row.view = mDialog.getLayoutInflater().inflate(R.layout.volume_dialog_row, null); row.view.setId(row.stream); row.view.setTag(row); - row.header = (TextView) row.view.findViewById(R.id.volume_row_header); + row.header = row.view.findViewById(R.id.volume_row_header); row.header.setId(20 * row.stream); - row.slider = (SeekBar) row.view.findViewById(R.id.volume_row_slider); + if (stream == STREAM_ACCESSIBILITY) { + row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)}); + } + row.dndIcon = row.view.findViewById(R.id.dnd_icon); + row.slider = row.view.findViewById(R.id.volume_row_slider); row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row)); row.anim = null; - // forward events above the slider into the slider - row.view.setOnTouchListener(new OnTouchListener() { - private final Rect mSliderHitRect = new Rect(); - private boolean mDragging; - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouch(View v, MotionEvent event) { - row.slider.getHitRect(mSliderHitRect); - if (!mDragging && event.getActionMasked() == MotionEvent.ACTION_DOWN - && event.getY() < mSliderHitRect.top) { - mDragging = true; - } - if (mDragging) { - event.offsetLocation(-mSliderHitRect.left, -mSliderHitRect.top); - row.slider.dispatchTouchEvent(event); - if (event.getActionMasked() == MotionEvent.ACTION_UP - || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { - mDragging = false; - } - return true; - } - return false; - } - }); + row.outputChooser = row.view.findViewById(R.id.output_chooser); + row.outputChooser.setOnClickListener(mClickOutputChooser); + row.connectedDevice = row.view.findViewById(R.id.volume_row_connected_device); + row.icon = row.view.findViewById(R.id.volume_row_icon); row.icon.setImageResource(iconRes); if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) { - row.icon.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - Events.writeEvent(mContext, Events.EVENT_ICON_CLICK, row.stream, row.iconState); - mController.setActiveStream(row.stream); - if (row.stream == AudioManager.STREAM_RING) { - final boolean hasVibrator = mController.hasVibrator(); - if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { - if (hasVibrator) { - mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); - } else { - final boolean wasZero = row.ss.level == 0; - mController.setStreamVolume(stream, - wasZero ? row.lastAudibleLevel : 0); - } + row.icon.setOnClickListener(v -> { + Events.writeEvent(mContext, Events.EVENT_ICON_CLICK, row.stream, row.iconState); + mController.setActiveStream(row.stream); + if (row.stream == AudioManager.STREAM_RING) { + final boolean hasVibrator = mController.hasVibrator(); + if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { + if (hasVibrator) { + mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); } else { - mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); - if (row.ss.level == 0) { - mController.setStreamVolume(stream, 1); - } + final boolean wasZero = row.ss.level == 0; + mController.setStreamVolume(stream, + wasZero ? row.lastAudibleLevel : 0); } } else { - final boolean vmute = row.ss.level == row.ss.levelMin; - mController.setStreamVolume(stream, - vmute ? row.lastAudibleLevel : row.ss.levelMin); + mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); + if (row.ss.level == 0) { + mController.setStreamVolume(stream, 1); + } } - row.userAttempt = 0; // reset the grace period, slider updates immediately + } else { + final boolean vmute = row.ss.level == row.ss.levelMin; + mController.setStreamVolume(stream, + vmute ? row.lastAudibleLevel : row.ss.levelMin); } + row.userAttempt = 0; // reset the grace period, slider updates immediately }); } else { row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); } } + public void initRingerH() { + mRingerIcon.setOnClickListener(v -> { + Events.writeEvent(mContext, Events.EVENT_ICON_CLICK, AudioManager.STREAM_RING, + mRingerIcon.getTag()); + final StreamState ss = mState.states.get(AudioManager.STREAM_RING); + if (ss == null) { + return; + } + // normal -> vibrate -> silent -> normal (skip vibrate if device doesn't have + // a vibrator. + final boolean hasVibrator = mController.hasVibrator(); + if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { + if (hasVibrator) { + mController.vibrate(); + mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); + } else { + mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false); + } + } else if (mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) { + mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false); + } else { + mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); + if (ss.level == 0) { + mController.setStreamVolume(AudioManager.STREAM_RING, 1); + } + } + updateRingerH(); + }); + mRingerIcon.setOnLongClickListener(v -> { + Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + dismissH(DISMISS_REASON_SETTINGS_CLICKED); + Dependency.get(ActivityStarter.class).startActivity(intent, true /* dismissShade */); + return true; + }); + updateRingerH(); + } + public void show(int reason) { mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget(); } @@ -495,7 +449,8 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { rescheduleTimeoutH(); if (mShowing) return; mShowing = true; - mMotion.startShow(); + + mDialog.show(); Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked()); mController.notifyVisible(true); } @@ -513,37 +468,29 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { if (mAccessibility.mFeedbackEnabled) return 20000; if (mHovering) return 16000; if (mSafetyWarning != null) return 5000; - if (mExpanded || mExpandButtonAnimationRunning) return 5000; - if (mActiveStream == AudioManager.STREAM_MUSIC) return 1500; - if (mZenFooter.shouldShowIntroduction()) { - return 6000; - } return 3000; } protected void dismissH(int reason) { - if (mMotion.isAnimating()) { - return; - } mHandler.removeMessages(H.DISMISS); mHandler.removeMessages(H.SHOW); if (!mShowing) return; + mDialogView.animate().cancel(); mShowing = false; - mMotion.startDismiss(new Runnable() { - @Override - public void run() { - updateExpandedH(false /* expanding */, true /* dismissing */); - } - }); - if (mAccessibilityMgr.isEnabled()) { - AccessibilityEvent event = - AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - event.setPackageName(mContext.getPackageName()); - event.setClassName(CustomDialog.class.getSuperclass().getName()); - event.getText().add(mContext.getString( - R.string.volume_dialog_accessibility_dismissed_message)); - mAccessibilityMgr.sendAccessibilityEvent(event); - } + + mDialogView.setTranslationX(0); + mDialogView.setAlpha(1); + mDialogView.animate() + .alpha(0) + .translationX(mDialogView.getWidth() / 2) + .setDuration(250) + .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator()) + .withEndAction(() -> mHandler.postDelayed(() -> { + if (D.BUG) Log.d(TAG, "mDialog.dismiss()"); + mDialog.dismiss(); + }, 50)) + .start(); + Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason); mController.notifyVisible(false); synchronized (mSafetyWarningLock) { @@ -554,81 +501,13 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { } } - private void updateDialogBottomMarginH() { - final long diff = System.currentTimeMillis() - mCollapseTime; - final boolean collapsing = mCollapseTime != 0 && diff < getConservativeCollapseDuration(); - final ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) mDialogView.getLayoutParams(); - final int bottomMargin = collapsing ? mDialogContentView.getHeight() : - mContext.getResources().getDimensionPixelSize(R.dimen.volume_dialog_margin_bottom); - if (bottomMargin != mlp.bottomMargin) { - if (D.BUG) Log.d(TAG, "bottomMargin " + mlp.bottomMargin + " -> " + bottomMargin); - mlp.bottomMargin = bottomMargin; - mDialogView.setLayoutParams(mlp); - } - } - - private long getConservativeCollapseDuration() { - return mExpandButtonAnimationDuration * 3; - } - - private void prepareForCollapse() { - mHandler.removeMessages(H.UPDATE_BOTTOM_MARGIN); - mCollapseTime = System.currentTimeMillis(); - updateDialogBottomMarginH(); - mHandler.sendEmptyMessageDelayed(H.UPDATE_BOTTOM_MARGIN, getConservativeCollapseDuration()); - } - - private void updateExpandedH(final boolean expanded, final boolean dismissing) { - if (mExpanded == expanded) return; - mExpanded = expanded; - mExpandButtonAnimationRunning = isAttached(); - if (D.BUG) Log.d(TAG, "updateExpandedH " + expanded); - updateExpandButtonH(); - updateFooterH(); - TransitionManager.endTransitions(mDialogView); - final VolumeRow activeRow = getActiveRow(); - if (!dismissing) { - mWindow.setLayout(mWindow.getAttributes().width, ViewGroup.LayoutParams.MATCH_PARENT); - TransitionManager.beginDelayedTransition(mDialogView, getTransition()); - } - updateRowsH(activeRow); - rescheduleTimeoutH(); + private boolean isAttached() { + return mDialogView != null && mDialogView.isAttachedToWindow(); } - private void updateExpandButtonH() { - if (D.BUG) Log.d(TAG, "updateExpandButtonH"); - mExpandButton.setClickable(!mExpandButtonAnimationRunning); - if (!(mExpandButtonAnimationRunning && isAttached())) { - final int res = mExpanded ? R.drawable.ic_volume_collapse_animation - : R.drawable.ic_volume_expand_animation; - if (hasTouchFeature()) { - mExpandButton.setImageResource(res); - } else { - // if there is no touch feature, show the volume ringer instead - mExpandButton.setImageResource(R.drawable.ic_volume_ringer); - mExpandButton.setBackgroundResource(0); // remove gray background emphasis - } - mExpandButton.setContentDescription(mContext.getString(mExpanded ? - R.string.accessibility_volume_collapse : R.string.accessibility_volume_expand)); - } - if (mExpandButtonAnimationRunning) { - final Drawable d = mExpandButton.getDrawable(); - if (d instanceof AnimatedVectorDrawable) { - // workaround to reset drawable - final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) d.getConstantState() - .newDrawable(); - mExpandButton.setImageDrawable(avd); - avd.start(); - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - mExpandButtonAnimationRunning = false; - updateExpandButtonH(); - rescheduleTimeoutH(); - } - }, mExpandButtonAnimationDuration); - } - } + private boolean hasTouchFeature() { + final PackageManager pm = mContext.getPackageManager(); + return pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN); } private boolean shouldBeVisibleH(VolumeRow row, VolumeRow activeRow) { @@ -638,15 +517,13 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { } // if the active row is accessibility, then continue to display previous - // active row since accessibility is dispalyed under it + // active row since accessibility is displayed under it if (activeRow.stream == AudioSystem.STREAM_ACCESSIBILITY && row.stream == mPrevActiveStream) { return true; } - return mExpanded && row.view.getVisibility() == View.VISIBLE - || (mExpanded && (row.important || isActive)) - || !mExpanded && isActive; + return row.defaultStream || isActive; } private void updateRowsH(final VolumeRow activeRow) { @@ -666,6 +543,87 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { } } + protected void updateConnectedDeviceH(String deviceName) { + for (final VolumeRow row : mRows) { + row.connectedDevice.setText(deviceName); + Util.setVisOrGone(row.connectedDevice, !TextUtils.isEmpty(deviceName)); + } + } + + protected void updateRingerH() { + if (mState != null) { + final StreamState ss = mState.states.get(AudioManager.STREAM_RING); + if (ss == null) { + return; + } + + enableRingerViewsH(mState.zenMode == Global.ZEN_MODE_OFF || !mState.disallowRinger); + switch (mState.ringerModeInternal) { + case AudioManager.RINGER_MODE_VIBRATE: + mRingerStatus.setText(R.string.volume_ringer_status_vibrate); + mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate); + mRingerIcon.setTag(Events.ICON_STATE_VIBRATE); + break; + case AudioManager.RINGER_MODE_SILENT: + mRingerStatus.setText(R.string.volume_ringer_status_silent); + mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); + mRingerIcon.setContentDescription(mContext.getString( + R.string.volume_stream_content_description_unmute, + getStreamLabelH(ss))); + mRingerIcon.setTag(Events.ICON_STATE_MUTE); + break; + case AudioManager.RINGER_MODE_NORMAL: + default: + boolean muted = (mAutomute && ss.level == 0) || ss.muted; + if (muted) { + mRingerStatus.setText(R.string.volume_ringer_status_silent); + mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); + mRingerIcon.setContentDescription(mContext.getString( + R.string.volume_stream_content_description_unmute, + getStreamLabelH(ss))); + mRingerIcon.setTag(Events.ICON_STATE_MUTE); + } else { + mRingerStatus.setText(R.string.volume_ringer_status_normal); + mRingerIcon.setImageResource(R.drawable.ic_volume_ringer); + if (mController.hasVibrator()) { + mRingerIcon.setContentDescription(mContext.getString( + mShowA11yStream + ? R.string.volume_stream_content_description_vibrate_a11y + : R.string.volume_stream_content_description_vibrate, + getStreamLabelH(ss))); + + } else { + mRingerIcon.setContentDescription(getStreamLabelH(ss)); + } + mRingerIcon.setTag(Events.ICON_STATE_UNMUTE); + } + break; + } + } + } + + /** + * Toggles enable state of views in a VolumeRow (not including seekbar, outputChooser or icon) + * Hides/shows zen icon + * @param enable whether to enable volume row views and hide dnd icon + */ + private void enableVolumeRowViewsH(VolumeRow row, boolean enable) { + row.header.setEnabled(enable); + row.dndIcon.setVisibility(enable ? View.GONE : View.VISIBLE); + } + + /** + * Toggles enable state of footer/ringer views + * Hides/shows zen icon + * @param enable whether to enable ringer views and hide dnd icon + */ + private void enableRingerViewsH(boolean enable) { + mRingerTitle.setEnabled(enable); + mRingerStatus.setEnabled(enable); + mRingerIcon.setEnabled(enable); + mZenIcon.setVisibility(enable ? View.GONE : View.VISIBLE); + } + private void trimObsoleteH() { if (D.BUG) Log.d(TAG, "trimObsoleteH"); for (int i = mRows.size() - 1; i >= 0; i--) { @@ -678,14 +636,8 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { } } - private void onStateChangedH(State state) { - final boolean animating = mMotion.isAnimating(); - if (D.BUG) Log.d(TAG, "onStateChangedH animating=" + animating); + protected void onStateChangedH(State state) { mState = state; - if (animating) { - mPendingStateChanged = true; - return; - } mDynamic.clear(); // add any new dynamic rows for (int i = 0; i < state.states.size(); i++) { @@ -695,7 +647,7 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { mDynamic.put(stream, true); if (findRow(stream) == null) { addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true, - true); + false, true); } } @@ -708,38 +660,9 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { for (VolumeRow row : mRows) { updateVolumeRowH(row); } - updateFooterH(); - } - - private void updateFooterH() { - if (D.BUG) Log.d(TAG, "updateFooterH"); - final boolean wasVisible = mZenFooter.getVisibility() == View.VISIBLE; - final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF - && (mAudioManager.isStreamAffectedByRingerMode(mActiveStream) || mExpanded) - && !mZenPanel.isEditing(); - - TransitionManager.endTransitions(mDialogView); - TransitionManager.beginDelayedTransition(mDialogView, getTransition()); - if (wasVisible != visible && !visible) { - prepareForCollapse(); - } - Util.setVisOrGone(mZenFooter, visible); - mZenFooter.update(); - - final boolean fullWasVisible = mZenPanel.getVisibility() == View.VISIBLE; - final boolean fullVisible = mShowFullZen && !visible; - if (fullWasVisible != fullVisible) { - Util.setVisOrGone(mZenPanel, fullVisible); - if (fullVisible) { - mZenPanel.setZenState(mState.zenMode); - mZenPanel.setDoneListener(new OnClickListener() { - @Override - public void onClick(View v) { - mHandler.sendEmptyMessage(H.UPDATE_FOOTER); - } - }); - } - } + updateRingerH(); + mWindow.setTitle(mContext.getString(R.string.volume_dialog_title, + getStreamLabelH(getActiveRow().ss))); } private void updateVolumeRowH(VolumeRow row) { @@ -754,7 +677,7 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { if (ss.level == row.requestedLevel) { row.requestedLevel = -1; } - final boolean isA11yStream = row.stream == AudioManager.STREAM_ACCESSIBILITY; + final boolean isA11yStream = row.stream == STREAM_ACCESSIBILITY; final boolean isRingStream = row.stream == AudioManager.STREAM_RING; final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM; final boolean isAlarmStream = row.stream == AudioManager.STREAM_ALARM; @@ -763,10 +686,14 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE; final boolean isRingSilent = isRingStream && mState.ringerModeInternal == AudioManager.RINGER_MODE_SILENT; + final boolean isZenPriorityOnly = mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; final boolean isZenAlarms = mState.zenMode == Global.ZEN_MODE_ALARMS; final boolean isZenNone = mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS; final boolean zenMuted = isZenAlarms ? (isRingStream || isSystemStream) : isZenNone ? (isRingStream || isSystemStream || isAlarmStream || isMusicStream) + : isZenPriorityOnly ? ((isAlarmStream && mState.disallowAlarms) || + (isMusicStream && mState.disallowMedia) || + (isRingStream && mState.disallowRinger)) : false; // update slider max @@ -786,19 +713,13 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { row.icon.setAlpha(iconEnabled ? 1 : 0.5f); final int iconRes = isRingVibrate ? R.drawable.ic_volume_ringer_vibrate - : isRingSilent || zenMuted ? row.cachedIconRes + : isRingSilent || zenMuted ? row.iconMuteRes : ss.routedToBluetooth ? (ss.muted ? R.drawable.ic_volume_media_bt_mute : R.drawable.ic_volume_media_bt) : mAutomute && ss.level == 0 ? row.iconMuteRes : (ss.muted ? row.iconMuteRes : row.iconRes); - if (iconRes != row.cachedIconRes) { - if (row.cachedIconRes != 0 && isRingVibrate) { - mController.vibrate(); - } - row.cachedIconRes = iconRes; - row.icon.setImageResource(iconRes); - } + row.icon.setImageResource(iconRes); row.iconState = iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes) @@ -850,6 +771,7 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { if (zenMuted) { row.tracking = false; } + enableVolumeRowViewsH(row, !zenMuted); // update slider final boolean enableSlider = !zenMuted; @@ -859,7 +781,7 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { } private void updateVolumeRowSliderTintH(VolumeRow row, boolean isActive) { - if (isActive && mExpanded) { + if (isActive) { row.slider.requestFocus(); } final ColorStateList tint = isActive && row.slider.isEnabled() ? mActiveSliderTint @@ -967,54 +889,43 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { rescheduleTimeoutH(); } + private void showOutputChooserH() { + synchronized (mOutputChooserLock) { + if (mOutputChooserDialog != null) { + return; + } + mOutputChooserDialog = new OutputChooserDialog(mContext, + new MediaRouterWrapper(MediaRouter.getInstance(mContext))) { + @Override + protected void cleanUp() { + synchronized (mOutputChooserLock) { + mOutputChooserDialog = null; + } + } + }; + mOutputChooserDialog.show(); + } + } + private String getStreamLabelH(StreamState ss) { if (ss.remoteLabel != null) { return ss.remoteLabel; } try { - return mContext.getString(ss.name); + return mContext.getResources().getString(ss.name); } catch (Resources.NotFoundException e) { Slog.e(TAG, "Can't find translation for stream " + ss); return ""; } } - private AutoTransition getTransition() { - AutoTransition transition = new AutoTransition(); - transition.setDuration(mExpandButtonAnimationDuration); - transition.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - transition.addListener(new Transition.TransitionListener() { - @Override - public void onTransitionStart(Transition transition) { - } - - @Override - public void onTransitionEnd(Transition transition) { - mWindow.setLayout( - mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT); - } - - @Override - public void onTransitionCancel(Transition transition) { - } - - @Override - public void onTransitionPause(Transition transition) { - mWindow.setLayout( - mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT); - } - - @Override - public void onTransitionResume(Transition transition) { - } - }); - return transition; - } - - private boolean hasTouchFeature() { - final PackageManager pm = mContext.getPackageManager(); - return pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN); - } + private final OnClickListener mClickOutputChooser = new OnClickListener() { + @Override + public void onClick(View v) { + dismissH(DISMISS_REASON_OUTPUT_CHOOSER); + showOutputChooserH(); + } + }; private final VolumeDialogController.Callbacks mControllerCallbackH = new VolumeDialogController.Callbacks() { @@ -1045,17 +956,9 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { @Override public void onConfigurationChanged() { - Configuration newConfig = mContext.getResources().getConfiguration(); - final int density = newConfig.densityDpi; - if (density != mDensity) { - mDialog.dismiss(); - mZenFooter.cleanup(); - initDialog(); - mDensity = density; - } - updateWindowWidthH(); + mDialog.dismiss(); + initDialog(); mConfigurableTexts.update(); - mZenFooter.onConfigurationChanged(); } @Override @@ -1079,42 +982,19 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { @Override public void onAccessibilityModeChanged(Boolean showA11yStream) { - boolean show = showA11yStream == null ? false : showA11yStream; - mShowA11yStream = show; + mShowA11yStream = showA11yStream == null ? false : showA11yStream; VolumeRow activeRow = getActiveRow(); - if (!mShowA11yStream && AudioManager.STREAM_ACCESSIBILITY == activeRow.stream) { + if (!mShowA11yStream && STREAM_ACCESSIBILITY == activeRow.stream) { dismissH(Events.DISMISS_STREAM_GONE); } else { updateRowsH(activeRow); } } - }; - - private final ZenModePanel.Callback mZenPanelCallback = new ZenModePanel.Callback() { - @Override - public void onPrioritySettings() { - mCallback.onZenPrioritySettingsClicked(); - } - - @Override - public void onInteraction() { - mHandler.sendEmptyMessage(H.RESCHEDULE_TIMEOUT); - } @Override - public void onExpanded(boolean expanded) { - // noop. - } - }; - - private final OnClickListener mClickExpand = new OnClickListener() { - @Override - public void onClick(View v) { - if (mExpandButtonAnimationRunning) return; - final boolean newExpand = !mExpanded; - Events.writeEvent(mContext, Events.EVENT_EXPAND, newExpand); - updateExpandedH(newExpand, false /* dismissing */); + public void onConnectedDeviceChanged(String deviceName) { + updateConnectedDeviceH(deviceName); } }; @@ -1126,8 +1006,6 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { private static final int SET_STREAM_IMPORTANT = 5; private static final int RESCHEDULE_TIMEOUT = 6; private static final int STATE_CHANGED = 7; - private static final int UPDATE_BOTTOM_MARGIN = 8; - private static final int UPDATE_FOOTER = 9; public H() { super(Looper.getMainLooper()); @@ -1143,15 +1021,13 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break; case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break; case STATE_CHANGED: onStateChangedH(mState); break; - case UPDATE_BOTTOM_MARGIN: updateDialogBottomMarginH(); break; - case UPDATE_FOOTER: updateFooterH(); break; } } } - private final class CustomDialog extends Dialog { + private final class CustomDialog extends Dialog implements DialogInterface { public CustomDialog(Context context) { - super(context); + super(context, com.android.systemui.R.style.qs_theme); } @Override @@ -1161,14 +1037,14 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { } @Override + protected void onStart() { + super.setCanceledOnTouchOutside(true); + super.onStart(); + } + + @Override protected void onStop() { super.onStop(); - final boolean animating = mMotion.isAnimating(); - if (D.BUG) Log.d(TAG, "onStop animating=" + animating); - if (animating) { - mPendingRecheckAll = true; - return; - } mHandler.sendEmptyMessage(H.RECHECK_ALL); } @@ -1182,27 +1058,6 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { } return false; } - - @Override - public boolean dispatchPopulateAccessibilityEvent(@NonNull AccessibilityEvent event) { - event.setClassName(getClass().getSuperclass().getName()); - event.setPackageName(mContext.getPackageName()); - - ViewGroup.LayoutParams params = getWindow().getAttributes(); - boolean isFullScreen = (params.width == ViewGroup.LayoutParams.MATCH_PARENT) && - (params.height == ViewGroup.LayoutParams.MATCH_PARENT); - event.setFullScreen(isFullScreen); - - if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { - if (mShowing) { - event.getText().add(mContext.getString( - R.string.volume_dialog_accessibility_shown_message, - getStreamLabelH(getActiveRow().ss))); - return true; - } - } - return false; - } } private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener { @@ -1323,11 +1178,14 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { private int iconRes; private int iconMuteRes; private boolean important; - private int cachedIconRes; + private boolean defaultStream; private ColorStateList cachedSliderTint; private int iconState; // from Events private ObjectAnimator anim; // slider progress animation for non-touch-related updates private int animTargetProgress; private int lastAudibleLevel = 1; + private View outputChooser; + private TextView connectedDevice; + private ImageView dndIcon; } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java deleted file mode 100644 index 01d31e2a9852..000000000000 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.volume; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.TimeInterpolator; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.app.Dialog; -import android.content.DialogInterface; -import android.content.DialogInterface.OnDismissListener; -import android.content.DialogInterface.OnShowListener; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.PathInterpolator; - -public class VolumeDialogMotion { - private static final String TAG = Util.logTag(VolumeDialogMotion.class); - - private static final float ANIMATION_SCALE = 1.0f; - private static final int PRE_DISMISS_DELAY = 50; - - private final Dialog mDialog; - private final View mDialogView; - private final ViewGroup mContents; // volume rows + zen footer - private final View mChevron; - private final Handler mHandler = new Handler(); - private final Callback mCallback; - - private boolean mAnimating; // show or dismiss animation is running - private boolean mShowing; // show animation is running - private boolean mDismissing; // dismiss animation is running - private ValueAnimator mChevronPositionAnimator; - private ValueAnimator mContentsPositionAnimator; - - public VolumeDialogMotion(Dialog dialog, View dialogView, ViewGroup contents, View chevron, - Callback callback) { - mDialog = dialog; - mDialogView = dialogView; - mContents = contents; - mChevron = chevron; - mCallback = callback; - mDialog.setOnDismissListener(new OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - if (D.BUG) Log.d(TAG, "mDialog.onDismiss"); - } - }); - mDialog.setOnShowListener(new OnShowListener() { - @Override - public void onShow(DialogInterface dialog) { - if (D.BUG) Log.d(TAG, "mDialog.onShow"); - final int h = mDialogView.getHeight(); - mDialogView.setTranslationY(-h); - startShowAnimation(); - } - }); - } - - public boolean isAnimating() { - return mAnimating; - } - - private void setShowing(boolean showing) { - if (showing == mShowing) return; - mShowing = showing; - if (D.BUG) Log.d(TAG, "mShowing = " + mShowing); - updateAnimating(); - } - - private void setDismissing(boolean dismissing) { - if (dismissing == mDismissing) return; - mDismissing = dismissing; - if (D.BUG) Log.d(TAG, "mDismissing = " + mDismissing); - updateAnimating(); - } - - private void updateAnimating() { - final boolean animating = mShowing || mDismissing; - if (animating == mAnimating) return; - mAnimating = animating; - if (D.BUG) Log.d(TAG, "mAnimating = " + mAnimating); - if (mCallback != null) { - mCallback.onAnimatingChanged(mAnimating); - } - } - - public void startShow() { - if (D.BUG) Log.d(TAG, "startShow"); - if (mShowing) return; - setShowing(true); - if (mDismissing) { - mDialogView.animate().cancel(); - setDismissing(false); - startShowAnimation(); - return; - } - if (D.BUG) Log.d(TAG, "mDialog.show()"); - mDialog.show(); - } - - private int chevronDistance() { - return mChevron.getHeight() / 6; - } - - private int chevronPosY() { - final Object tag = mChevron == null ? null : mChevron.getTag(); - return tag == null ? 0 : (Integer) tag; - } - - private void startShowAnimation() { - if (D.BUG) Log.d(TAG, "startShowAnimation"); - mDialogView.animate() - .translationY(0) - .setDuration(scaledDuration(300)) - .setInterpolator(new LogDecelerateInterpolator()) - .setListener(null) - .setUpdateListener(animation -> { - if (mChevronPositionAnimator != null) { - final float v = (Float) mChevronPositionAnimator.getAnimatedValue(); - if (mChevronPositionAnimator == null) return; - // reposition chevron - final int posY = chevronPosY(); - mChevron.setTranslationY(posY + v + -mDialogView.getTranslationY()); - } - }) - .withEndAction(new Runnable() { - @Override - public void run() { - if (mChevronPositionAnimator == null) return; - // reposition chevron - final int posY = chevronPosY(); - mChevron.setTranslationY(posY + -mDialogView.getTranslationY()); - } - }) - .start(); - - mContentsPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0) - .setDuration(scaledDuration(400)); - mContentsPositionAnimator.addListener(new AnimatorListenerAdapter() { - private boolean mCancelled; - - @Override - public void onAnimationEnd(Animator animation) { - if (mCancelled) return; - if (D.BUG) Log.d(TAG, "show.onAnimationEnd"); - setShowing(false); - } - @Override - public void onAnimationCancel(Animator animation) { - if (D.BUG) Log.d(TAG, "show.onAnimationCancel"); - mCancelled = true; - } - }); - mContentsPositionAnimator.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float v = (Float) animation.getAnimatedValue(); - mContents.setTranslationY(v + -mDialogView.getTranslationY()); - } - }); - mContentsPositionAnimator.setInterpolator(new LogDecelerateInterpolator()); - mContentsPositionAnimator.start(); - - mContents.setAlpha(0); - mContents.animate() - .alpha(1) - .setDuration(scaledDuration(150)) - .setInterpolator(new PathInterpolator(0f, 0f, .2f, 1f)) - .start(); - - mChevronPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0) - .setDuration(scaledDuration(250)); - mChevronPositionAnimator.setInterpolator(new PathInterpolator(.4f, 0f, .2f, 1f)); - mChevronPositionAnimator.start(); - - mChevron.setAlpha(0); - mChevron.animate() - .alpha(1) - .setStartDelay(scaledDuration(50)) - .setDuration(scaledDuration(150)) - .setInterpolator(new PathInterpolator(.4f, 0f, 1f, 1f)) - .start(); - } - - public void startDismiss(final Runnable onComplete) { - if (D.BUG) Log.d(TAG, "startDismiss"); - if (mDismissing) return; - setDismissing(true); - if (mShowing) { - mDialogView.animate().cancel(); - if (mContentsPositionAnimator != null) { - mContentsPositionAnimator.cancel(); - } - mContents.animate().cancel(); - if (mChevronPositionAnimator != null) { - mChevronPositionAnimator.cancel(); - } - mChevron.animate().cancel(); - setShowing(false); - } - mDialogView.animate() - .translationY(-mDialogView.getHeight()) - .setDuration(scaledDuration(250)) - .setInterpolator(new LogAccelerateInterpolator()) - .setUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - mContents.setTranslationY(-mDialogView.getTranslationY()); - final int posY = chevronPosY(); - mChevron.setTranslationY(posY + -mDialogView.getTranslationY()); - } - }) - .setListener(new AnimatorListenerAdapter() { - private boolean mCancelled; - @Override - public void onAnimationEnd(Animator animation) { - if (mCancelled) return; - if (D.BUG) Log.d(TAG, "dismiss.onAnimationEnd"); - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - if (D.BUG) Log.d(TAG, "mDialog.dismiss()"); - mDialog.dismiss(); - onComplete.run(); - setDismissing(false); - } - }, PRE_DISMISS_DELAY); - - } - @Override - public void onAnimationCancel(Animator animation) { - if (D.BUG) Log.d(TAG, "dismiss.onAnimationCancel"); - mCancelled = true; - } - }).start(); - } - - private static int scaledDuration(int base) { - return (int) (base * ANIMATION_SCALE); - } - - public static final class LogDecelerateInterpolator implements TimeInterpolator { - private final float mBase; - private final float mDrift; - private final float mTimeScale; - private final float mOutputScale; - - public LogDecelerateInterpolator() { - this(400f, 1.4f, 0); - } - - private LogDecelerateInterpolator(float base, float timeScale, float drift) { - mBase = base; - mDrift = drift; - mTimeScale = 1f / timeScale; - - mOutputScale = 1f / computeLog(1f); - } - - private float computeLog(float t) { - return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t); - } - - @Override - public float getInterpolation(float t) { - return computeLog(t) * mOutputScale; - } - } - - public static final class LogAccelerateInterpolator implements TimeInterpolator { - private final int mBase; - private final int mDrift; - private final float mLogScale; - - public LogAccelerateInterpolator() { - this(100, 0); - } - - private LogAccelerateInterpolator(int base, int drift) { - mBase = base; - mDrift = drift; - mLogScale = 1f / computeLog(1, mBase, mDrift); - } - - private static float computeLog(float t, int base, int drift) { - return (float) -Math.pow(base, -t) + 1 + (drift * t); - } - - @Override - public float getInterpolation(float t) { - return 1 - computeLog(1 - t, mBase, mDrift) * mLogScale; - } - } - - public interface Callback { - void onAnimatingChanged(boolean animating); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java index 02969e483e97..6f65b081afc4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java @@ -40,9 +40,13 @@ public class VolumeUI extends SystemUI { @Override public void start() { - mEnabled = mContext.getResources().getBoolean(R.bool.enable_volume_ui); + boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui); + boolean enableSafetyWarning = + mContext.getResources().getBoolean(R.bool.enable_safety_warning); + mEnabled = enableVolumeUi || enableSafetyWarning; if (!mEnabled) return; mVolumeComponent = new VolumeDialogComponent(this, mContext, null); + mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning); putComponent(VolumeComponent.class, getVolumeComponent()); setDefaultVolumeController(); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java new file mode 100644 index 000000000000..0a3a2ab7c61f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java @@ -0,0 +1,153 @@ +/* + * 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.volume; + +import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.DisplayCutout; +import android.view.View; +import android.view.ViewOutlineProvider; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; + +import com.android.systemui.R; +import com.android.systemui.util.leak.RotationUtils; + +public class VolumeUiLayout extends FrameLayout { + + private View mChild; + private int mRotation = ROTATION_NONE; + @Nullable + private DisplayCutout mDisplayCutout; + + public VolumeUiLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mDisplayCutout = null; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (mChild == null) { + if (getChildCount() != 0) { + mChild = getChildAt(0); + updateRotation(); + } else { + return; + } + } + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateRotation(); + } + + private void setDisplayCutout() { + if (mDisplayCutout == null && getRootWindowInsets() != null) { + DisplayCutout cutout = getRootWindowInsets().getDisplayCutout(); + if (cutout != null) { + mDisplayCutout = cutout; + } + } + } + + public void updateRotation() { + setDisplayCutout(); + if (mChild == null) { + if (getChildCount() != 0) { + mChild = getChildAt(0); + } + } + int rotation = RotationUtils.getRotation(getContext()); + if (rotation != mRotation) { + updateSafeInsets(rotation); + mRotation = rotation; + } + } + + private void updateSafeInsets(int rotation) { + // Depending on our rotation, we may have to work around letterboxing from the right + // side from the navigation bar or a cutout. + + MarginLayoutParams lp = (MarginLayoutParams) mChild.getLayoutParams(); + + int margin = (int) getResources().getDimension(R.dimen.volume_dialog_base_margin); + switch (rotation) { + /* + * Landscape: <-|. Have to deal with the nav bar + * Seascape: |->. Have to deal with the cutout + */ + case RotationUtils.ROTATION_LANDSCAPE: + margin += getNavBarHeight(); + break; + case RotationUtils.ROTATION_SEASCAPE: + margin += getDisplayCutoutHeight(); + break; + default: + break; + } + + lp.rightMargin = margin; + mChild.setLayoutParams(lp); + } + + private int getNavBarHeight() { + return (int) getResources().getDimension(R.dimen.navigation_bar_size); + } + + //TODO: Find a better way + private int getDisplayCutoutHeight() { + if (mDisplayCutout == null || mDisplayCutout.isEmpty()) { + return 0; + } + + Rect r = mDisplayCutout.getBoundingRect(); + return r.bottom - r.top; + } + + @Override + public ViewOutlineProvider getOutlineProvider() { + return super.getOutlineProvider(); + } + + public static VolumeUiLayout get(View v) { + if (v instanceof VolumeUiLayout) return (VolumeUiLayout) v; + if (v.getParent() instanceof View) { + return get((View) v.getParent()); + } + return null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java deleted file mode 100644 index 80e162983a65..000000000000 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.volume; - -import android.animation.LayoutTransition; -import android.animation.ValueAnimator; -import android.content.Context; -import android.provider.Settings.Global; -import android.service.notification.ZenModeConfig; -import android.transition.AutoTransition; -import android.transition.TransitionManager; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.systemui.Prefs; -import com.android.systemui.R; -import com.android.systemui.statusbar.policy.ZenModeController; - -import java.util.Objects; - -/** - * Zen mode information (and end button) attached to the bottom of the volume dialog. - */ -public class ZenFooter extends LinearLayout { - private static final String TAG = Util.logTag(ZenFooter.class); - - private final Context mContext; - private final ConfigurableTexts mConfigurableTexts; - - private ImageView mIcon; - private TextView mSummaryLine1; - private TextView mSummaryLine2; - private TextView mEndNowButton; - private View mZenIntroduction; - private View mZenIntroductionConfirm; - private TextView mZenIntroductionMessage; - private int mZen = -1; - private ZenModeConfig mConfig; - private ZenModeController mController; - - public ZenFooter(Context context, AttributeSet attrs) { - super(context, attrs); - mContext = context; - mConfigurableTexts = new ConfigurableTexts(mContext); - final LayoutTransition layoutTransition = new LayoutTransition(); - layoutTransition.setDuration(new ValueAnimator().getDuration() / 2); - setLayoutTransition(layoutTransition); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mIcon = findViewById(R.id.volume_zen_icon); - mSummaryLine1 = findViewById(R.id.volume_zen_summary_line_1); - mSummaryLine2 = findViewById(R.id.volume_zen_summary_line_2); - mEndNowButton = findViewById(R.id.volume_zen_end_now); - mZenIntroduction = findViewById(R.id.zen_introduction); - mZenIntroductionMessage = findViewById(R.id.zen_introduction_message); - mConfigurableTexts.add(mZenIntroductionMessage, R.string.zen_alarms_introduction); - mZenIntroductionConfirm = findViewById(R.id.zen_introduction_confirm); - mZenIntroductionConfirm.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - confirmZenIntroduction(); - } - }); - Util.setVisOrGone(mZenIntroduction, shouldShowIntroduction()); - mConfigurableTexts.add(mSummaryLine1); - mConfigurableTexts.add(mSummaryLine2); - mConfigurableTexts.add(mEndNowButton, R.string.volume_zen_end_now); - } - - public void init(final ZenModeController controller) { - mEndNowButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - setZen(Global.ZEN_MODE_OFF); - controller.setZen(Global.ZEN_MODE_OFF, null, TAG); - } - }); - mZen = controller.getZen(); - mConfig = controller.getConfig(); - mController = controller; - mController.addCallback(mZenCallback); - update(); - updateIntroduction(); - } - - public void cleanup() { - mController.removeCallback(mZenCallback); - } - - private void setZen(int zen) { - if (mZen == zen) return; - mZen = zen; - update(); - post(() -> { - updateIntroduction(); - }); - } - - private void setConfig(ZenModeConfig config) { - if (Objects.equals(mConfig, config)) return; - mConfig = config; - update(); - } - - private void confirmZenIntroduction() { - Prefs.putBoolean(mContext, Prefs.Key.DND_CONFIRMED_ALARM_INTRODUCTION, true); - updateIntroduction(); - } - - private boolean isZenPriority() { - return mZen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; - } - - private boolean isZenAlarms() { - return mZen == Global.ZEN_MODE_ALARMS; - } - - private boolean isZenNone() { - return mZen == Global.ZEN_MODE_NO_INTERRUPTIONS; - } - - public void update() { - mIcon.setImageResource(isZenNone() ? R.drawable.ic_dnd_total_silence : R.drawable.ic_dnd); - final String line1 = - isZenPriority() ? mContext.getString(R.string.interruption_level_priority) - : isZenAlarms() ? mContext.getString(R.string.interruption_level_alarms) - : isZenNone() ? mContext.getString(R.string.interruption_level_none) - : null; - Util.setText(mSummaryLine1, line1); - - final CharSequence line2 = ZenModeConfig.getConditionSummary(mContext, mConfig, - mController.getCurrentUser(), true /*shortVersion*/); - Util.setText(mSummaryLine2, line2); - } - - public boolean shouldShowIntroduction() { - final boolean confirmed = Prefs.getBoolean(mContext, - Prefs.Key.DND_CONFIRMED_ALARM_INTRODUCTION, false); - return !confirmed && isZenAlarms(); - } - - public void updateIntroduction() { - Util.setVisOrGone(mZenIntroduction, shouldShowIntroduction()); - } - - public void onConfigurationChanged() { - mConfigurableTexts.update(); - } - - private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { - @Override - public void onZenChanged(int zen) { - setZen(zen); - } - @Override - public void onConfigChanged(ZenModeConfig config) { - setConfig(config); - } - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenRadioLayout.java b/packages/SystemUI/src/com/android/systemui/volume/ZenRadioLayout.java deleted file mode 100644 index 360907b0d22b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenRadioLayout.java +++ /dev/null @@ -1,93 +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 com.android.systemui.volume; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; - -/** - * Specialized layout for zen mode that allows the radio buttons to reside within - * a RadioGroup, but also makes sure that all the heights off the radio buttons align - * with the corresponding content in the second child of this view. - */ -public class ZenRadioLayout extends LinearLayout { - - public ZenRadioLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - /** - * Run 2 measurement passes, 1 that figures out the size of the content, and another - * that sets the size of the radio buttons to the heights of the corresponding content. - */ - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - ViewGroup radioGroup = (ViewGroup) getChildAt(0); - ViewGroup radioContent = (ViewGroup) getChildAt(1); - int size = radioGroup.getChildCount(); - if (size != radioContent.getChildCount()) { - throw new IllegalStateException("Expected matching children"); - } - boolean hasChanges = false; - View lastView = null; - for (int i = 0; i < size; i++) { - View radio = radioGroup.getChildAt(i); - View content = radioContent.getChildAt(i); - if (lastView != null) { - radio.setAccessibilityTraversalAfter(lastView.getId()); - } - View contentClick = findFirstClickable(content); - if (contentClick != null) contentClick.setAccessibilityTraversalAfter(radio.getId()); - lastView = findLastClickable(content); - if (radio.getLayoutParams().height != content.getMeasuredHeight()) { - hasChanges = true; - radio.getLayoutParams().height = content.getMeasuredHeight(); - } - } - // Measure again if any heights changed. - if (hasChanges) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - - private View findFirstClickable(View content) { - if (content.isClickable()) return content; - if (content instanceof ViewGroup) { - ViewGroup group = (ViewGroup) content; - for (int i = 0; i < group.getChildCount(); i++) { - View v = findFirstClickable(group.getChildAt(i)); - if (v != null) return v; - } - } - return null; - } - - private View findLastClickable(View content) { - if (content.isClickable()) return content; - if (content instanceof ViewGroup) { - ViewGroup group = (ViewGroup) content; - for (int i = group.getChildCount() - 1; i >= 0; i--) { - View v = findLastClickable(group.getChildAt(i)); - if (v != null) return v; - } - } - return null; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java deleted file mode 100644 index d28e42e61f29..000000000000 --- a/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.volume.car; - -import android.car.Car; -import android.car.CarNotConnectedException; -import android.car.media.CarAudioManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.ServiceConnection; -import android.os.IBinder; -import android.util.Log; - -import com.android.systemui.volume.VolumeDialogControllerImpl; - -/** - * A volume dialog controller for the automotive use case. - * - * {@link android.car.media.CarAudioManager} is the source of truth to get the stream volumes. - * And volume changes should be sent to the car's audio module instead of the android's audio mixer. - */ -public class CarVolumeDialogController extends VolumeDialogControllerImpl { - private static final String TAG = "CarVolumeDialogController"; - - private final Car mCar; - private CarAudioManager mCarAudioManager; - - private final ServiceConnection mConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName componentName, IBinder iBinder) { - try { - mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE); - setVolumeController(); - CarVolumeDialogController.this.getState(); - } catch (CarNotConnectedException e) { - Log.e(TAG, "Car is not connected!", e); - } - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - Log.e(TAG, "Car service is disconnected"); - } - }; - - public CarVolumeDialogController(Context context) { - super(context); - mCar = Car.createCar(context, mConnection); - mCar.connect(); - } - - @Override - protected void setAudioManagerStreamVolume(int stream, int level, int flag) { - if (mCarAudioManager == null) { - Log.d(TAG, "Car audio manager is not initialized yet"); - return; - } - try { - mCarAudioManager.setStreamVolume(stream, level, flag); - } catch (CarNotConnectedException e) { - Log.e(TAG, "Car is not connected", e); - } - } - - @Override - protected int getAudioManagerStreamVolume(int stream) { - if(mCarAudioManager == null) { - Log.d(TAG, "Car audio manager is not initialized yet"); - return 0; - } - - try { - return mCarAudioManager.getStreamVolume(stream); - } catch (CarNotConnectedException e) { - Log.e(TAG, "Car is not connected", e); - return 0; - } - } - - @Override - protected int getAudioManagerStreamMaxVolume(int stream) { - if(mCarAudioManager == null) { - Log.d(TAG, "Car audio manager is not initialized yet"); - return 0; - } - - try { - return mCarAudioManager.getStreamMaxVolume(stream); - } catch (CarNotConnectedException e) { - Log.e(TAG, "Car is not connected", e); - return 0; - } - } - - @Override - protected int getAudioManagerStreamMinVolume(int stream) { - if(mCarAudioManager == null) { - Log.d(TAG, "Car audio manager is not initialized yet"); - return 0; - } - - try { - return mCarAudioManager.getStreamMinVolume(stream); - } catch (CarNotConnectedException e) { - Log.e(TAG, "Car is not connected", e); - return 0; - } - } - - @Override - public void setVolumeController() { - if (mCarAudioManager == null) { - Log.d(TAG, "Car audio manager is not initialized yet"); - return; - } - try { - mCarAudioManager.setVolumeController(mVolumeController); - } catch (CarNotConnectedException e) { - Log.e(TAG, "Car is not connected", e); - } - } -} |