Merge "Image inline reply" into sc-dev

This commit is contained in:
Jay Aliomer
2021-04-20 16:15:31 +00:00
committed by Android (Google) Code Review
6 changed files with 234 additions and 74 deletions

View File

@@ -0,0 +1,22 @@
<!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="48dp"
android:width="48dp" />
<solid android:color="@android:color/transparent" />
<corners android:radius="4dp"></corners>
</shape>

View File

@@ -0,0 +1,23 @@
<!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="oval">
<size android:height="24dp"
android:width="24dp" />
<solid android:color="#FFFFFF" />
</shape>

View File

@@ -22,28 +22,75 @@
android:id="@+id/remote_input"
android:layout_height="match_parent"
android:layout_width="match_parent">
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/remote_input_content"
android:orientation="vertical"
android:clipToPadding="false"
android:layout_marginEnd="20dp"
android:layout_marginStart="20dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="16dp"
android:layout_weight="1">
<FrameLayout
android:id="@+id/remote_input_content_container"
android:layout_marginTop="-6dp"
android:layout_height="60dp"
android:layout_width="60dp"
android:layout_marginStart="4dp"
android:layout_marginBottom="12dp"
android:visibility="gone"
android:layout_gravity="center_vertical">
<ImageView
android:id="@+id/remote_input_attachment_image"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="start|bottom"
android:clipToOutline="true"
android:background="@drawable/rounded_rect_background"
android:scaleType="centerCrop" />
<ImageView
android:id="@+id/remote_input_delete_bg"
android:paddingStart="24dp"
android:paddingBottom="24dp"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="end|top"
android:src="@drawable/thumbnail_delete_btn_bg"/>
<ImageView
android:id="@+id/remote_input_delete"
android:paddingTop="3dp"
android:paddingEnd="3dp"
android:paddingStart="27dp"
android:paddingBottom="27dp"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="end|top"
android:src="@drawable/ic_close"
android:contentDescription="@string/notif_inline_reply_remove_attachment_description"/>
</FrameLayout>
<view class="com.android.systemui.statusbar.policy.RemoteInputView$RemoteEditText"
android:id="@+id/remote_input_text"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:paddingTop="2dp"
android:paddingStart="16dp"
android:paddingEnd="12dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="16dp"
android:layout_gravity="start|center_vertical"
android:textAppearance="?android:attr/textAppearance"
android:textColor="@color/remote_input_text"
android:textColorHint="@color/remote_input_hint"
android:textSize="16sp"
android:background="@null"
android:maxLines="4"
android:ellipsize="start"
android:inputType="textShortMessage|textMultiLine|textAutoCorrect|textCapSentences"
android:imeOptions="actionSend|flagNoExtractUi|flagNoFullscreen" />
<view class="com.android.systemui.statusbar.policy.RemoteInputView$RemoteEditText"
android:id="@+id/remote_input_text"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_weight="1"
android:paddingTop="2dp"
android:paddingStart="4dp"
android:paddingBottom="4dp"
android:paddingEnd="12dp"
android:layout_gravity="start|center_vertical"
android:textAppearance="?android:attr/textAppearance"
android:textColor="@color/remote_input_text"
android:textColorHint="@color/remote_input_hint"
android:textSize="16sp"
android:background="@null"
android:maxLines="4"
android:ellipsize="start"
android:inputType="textShortMessage|textMultiLine|textAutoCorrect|textCapSentences"
android:imeOptions="actionSend|flagNoExtractUi|flagNoFullscreen" />
</LinearLayout>
<FrameLayout
android:layout_width="wrap_content"

View File

@@ -2096,7 +2096,8 @@
<string name="keyboard_key_num_lock">Num Lock</string>
<!-- Name used to refer to keys on the numeric pad of the keyboard, e.g. "Numpad 9". -->
<string name="keyboard_key_numpad_template">Numpad <xliff:g id="name">%1$s</xliff:g></string>
<!-- Content description for the delete button on an image attachment when using inline reply via notification [CHAR LIMIT=NONE] -->
<string name="notif_inline_reply_remove_attachment_description">Remove attachment</string>
<!-- User visible title for the system-wide keyboard shortcuts list. -->
<string name="keyboard_shortcut_group_system">System</string>
<!-- User visible title for the keyboard shortcut that takes the user to the home screen. -->

View File

@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.row;
import android.annotation.ColorInt;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.view.View;
@@ -93,14 +94,16 @@ public class FooterView extends StackScrollerDecorView {
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
int textColor = getResources().getColor(R.color.notif_pill_text);
Resources.Theme theme = getContext().getTheme();
mDismissButton.setBackground(
getResources().getDrawable(R.drawable.notif_footer_btn_background));
mDismissButton.setTextColor(getResources().getColor(R.color.notif_pill_text));
getResources().getDrawable(R.drawable.notif_footer_btn_background, theme));
mDismissButton.setTextColor(textColor);
mManageButton.setBackground(
getResources().getDrawable(R.drawable.notif_footer_btn_background));
mManageButton.setTextColor(getResources().getColor(R.color.notif_pill_text));
getResources().getDrawable(R.drawable.notif_footer_btn_background, theme));
mManageButton = findViewById(R.id.manage_text);
mDismissButton.setText(R.string.clear_all_notifications_text);
mManageButton.setTextColor(textColor);
mDismissButton.setContentDescription(
mContext.getString(R.string.accessibility_clear_all));
showHistory(mShowHistory);

View File

@@ -25,21 +25,18 @@ import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.BlendMode;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.ServiceManager;
@@ -47,7 +44,9 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.text.Editable;
import android.text.SpannedString;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
@@ -68,6 +67,7 @@ import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
@@ -81,7 +81,6 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -89,6 +88,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry.
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.wm.shell.animation.Interpolators;
import java.util.ArrayList;
import java.util.Collection;
@@ -114,6 +114,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private final List<OnFocusChangeListener> mEditTextFocusChangeListeners = new ArrayList<>();
private RemoteEditText mEditText;
private ImageButton mSendButton;
private GradientDrawable mContentBackground;
private ProgressBar mProgressBar;
private PendingIntent mPendingIntent;
private RemoteInput[] mRemoteInputs;
@@ -130,6 +131,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private int mRevealCx;
private int mRevealCy;
private int mRevealR;
private ContentInfo mAttachment;
private boolean mColorized;
private int mTint;
@@ -138,6 +140,9 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private NotificationViewWrapper mWrapper;
private Consumer<Boolean> mOnVisibilityChangedListener;
private NotificationRemoteInputManager.BouncerChecker mBouncerChecker;
private LinearLayout mContentView;
private ImageView mDelete;
private ImageView mDeleteBg;
public RemoteInputView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -149,7 +154,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
TypedArray ta = getContext().getTheme().obtainStyledAttributes(new int[]{
com.android.internal.R.attr.colorAccent,
com.android.internal.R.attr.colorBackgroundFloating,
com.android.internal.R.attr.colorSurface,
});
mTint = ta.getColor(0, 0);
ta.recycle();
@@ -165,14 +170,20 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mColorized = colorized;
mTint = backgroundColor;
final int editBgColor;
final int alternateBgColor;
final int alternateTextColor;
final int accentColor;
final int textColor;
final int hintTextColor;
final int stroke = mContext.getResources().getDimensionPixelSize(
R.dimen.remote_input_view_text_stroke);
if (colorized) {
final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor);
final int foregroundColor = dark ? Color.WHITE : Color.BLACK;
editBgColor = backgroundColor;
accentColor = foregroundColor;
alternateBgColor = foregroundColor;
alternateTextColor = backgroundColor;
textColor = foregroundColor;
hintTextColor = ColorUtils.setAlphaComponent(foregroundColor, 0x99);
} else {
@@ -180,10 +191,14 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
hintTextColor = mContext.getColor(R.color.remote_input_hint);
try (TypedArray ta = getContext().getTheme().obtainStyledAttributes(new int[]{
com.android.internal.R.attr.colorAccent,
com.android.internal.R.attr.colorBackgroundFloating,
com.android.internal.R.attr.colorSurface,
com.android.internal.R.attr.colorSurfaceVariant,
com.android.internal.R.attr.textColorPrimary
})) {
accentColor = ta.getColor(0, textColor);
editBgColor = ta.getColor(1, backgroundColor);
alternateBgColor = ta.getColor(2, textColor);
alternateTextColor = ta.getColor(3, backgroundColor);
}
}
mEditText.setAllColors(backgroundColor, editBgColor,
@@ -195,11 +210,15 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
accentColor,
accentColor & 0x4DFFFFFF // %30 opacity
});
mContentBackground.setColor(editBgColor);
mContentBackground.setStroke(stroke, accentTint);
mDelete.setImageTintList(ColorStateList.valueOf(alternateTextColor));
mDeleteBg.setImageTintList(ColorStateList.valueOf(alternateBgColor));
mSendButton.setImageTintList(accentTint);
mProgressBar.setProgressTintList(accentTint);
mProgressBar.setIndeterminateTintList(accentTint);
mProgressBar.setSecondaryProgressTintList(accentTint);
setBackgroundColor(backgroundColor);
setBackgroundColor(editBgColor);
}
@Override
@@ -209,8 +228,16 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mProgressBar = findViewById(R.id.remote_input_progress);
mSendButton = findViewById(R.id.remote_input_send);
mSendButton.setOnClickListener(this);
mEditText = (RemoteEditText) getChildAt(0);
mContentBackground = (GradientDrawable)
mContext.getDrawable(R.drawable.remote_input_view_text_bg).mutate();
mDelete = findViewById(R.id.remote_input_delete);
mDeleteBg = findViewById(R.id.remote_input_delete_bg);
mDeleteBg.setImageTintBlendMode(BlendMode.SRC_IN);
mDelete.setImageTintBlendMode(BlendMode.SRC_IN);
mDelete.setOnClickListener(v -> setAttachment(null));
mContentView = findViewById(R.id.remote_input_content);
mContentView.setBackground(mContentBackground);
mEditText = findViewById(R.id.remote_input_text);
mEditText.setInnerFocusable(false);
mEditText.setWindowInsetsAnimationCallback(
new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
@@ -220,7 +247,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
@NonNull List<WindowInsetsAnimation> runningAnimations) {
return insets;
}
@Override
public void onEnd(@NonNull WindowInsetsAnimation animation) {
super.onEnd(animation);
@@ -235,7 +261,43 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
});
}
protected Intent prepareRemoteInputFromText() {
private void setAttachment(ContentInfo item) {
if (mAttachment != null) {
// We need to release permissions when sending the attachment to the target
// app or if it is deleted by the user. When sending to the target app, we
// can safely release permissions as soon as the call to
// `mController.grantInlineReplyUriPermission` is made (ie, after the grant
// to the target app has been created).
mAttachment.releasePermissions();
}
mAttachment = item;
View attachment = findViewById(R.id.remote_input_content_container);
ImageView iconView = findViewById(R.id.remote_input_attachment_image);
iconView.setImageDrawable(null);
if (item == null) {
attachment.setVisibility(GONE);
return;
}
iconView.setImageURI(item.getClip().getItemAt(0).getUri());
if (iconView.getDrawable() == null) {
attachment.setVisibility(GONE);
} else {
attachment.setVisibility(VISIBLE);
}
}
/**
* Reply intent
* @return returns intent with granted URI permissions that should be used immediately
*/
private Intent prepareRemoteInput() {
if (mAttachment == null) return prepareRemoteInputFromText();
return prepareRemoteInputFromData(
mAttachment.getClip().getDescription().getMimeType(0),
mAttachment.getClip().getItemAt(0).getUri());
}
private Intent prepareRemoteInputFromText() {
Bundle results = new Bundle();
results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -255,17 +317,40 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
return fillInIntent;
}
protected Intent prepareRemoteInputFromData(String contentType, Uri data) {
private Intent prepareRemoteInputFromData(String contentType, Uri data) {
HashMap<String, Uri> results = new HashMap<>();
results.put(contentType, data);
// grant for the target app.
mController.grantInlineReplyUriPermission(mEntry.getSbn(), data);
Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
RemoteInput.addDataResultToIntent(mRemoteInput, fillInIntent, results);
mEntry.remoteInputText = mContext.getString(R.string.remote_input_image_insertion_text);
Bundle bundle = new Bundle();
bundle.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
bundle);
CharSequence attachmentText = mAttachment.getClip().getDescription().getLabel();
CharSequence attachmentLabel = TextUtils.isEmpty(attachmentText)
? mContext.getString(R.string.remote_input_image_insertion_text)
: attachmentText;
// add content description to reply text for context
CharSequence fullText = TextUtils.isEmpty(mEditText.getText())
? attachmentLabel
: "\"" + attachmentLabel + "\" " + mEditText.getText();
mEntry.remoteInputText = fullText;
mEntry.remoteInputMimeType = contentType;
mEntry.remoteInputUri = data;
// mirror prepareRemoteInputFromText for text input
if (mEntry.editedSuggestionInfo == null) {
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT);
} else if (mAttachment == null) {
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_CHOICE);
}
return fillInIntent;
}
@@ -303,6 +388,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_FAIL,
mEntry.getSbn().getPackageName());
}
setAttachment(null);
}
public CharSequence getText() {
@@ -327,7 +413,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
@Override
public void onClick(View v) {
if (v == mSendButton) {
sendRemoteInput(prepareRemoteInputFromText());
sendRemoteInput(prepareRemoteInput());
}
}
@@ -682,8 +768,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
&& event.getAction() == KeyEvent.ACTION_DOWN;
if (isSoftImeEvent || isKeyboardEnterKey) {
if (mEditText.length() > 0) {
sendRemoteInput(prepareRemoteInputFromText());
if (mEditText.length() > 0 || mAttachment != null) {
sendRemoteInput(prepareRemoteInput());
}
// Consume action to prevent IME from closing.
return true;
@@ -716,21 +802,18 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private final OnReceiveContentListener mOnReceiveContentListener = this::onReceiveContent;
private RemoteInputView mRemoteInputView;
private GradientDrawable mTextBackground;
private ColorDrawable mBackgroundColor;
private LayerDrawable mBackground;
private ColorDrawable mBackground;
boolean mShowImeOnInputConnection;
private LightBarController mLightBarController;
private InputMethodManager mInputMethodManager;
private ArraySet<String> mSupportedMimes = new ArraySet<>();
UserHandle mUser;
public RemoteEditText(Context context, AttributeSet attrs) {
super(context, attrs);
mLightBarController = Dependency.get(LightBarController.class);
mTextBackground = (GradientDrawable)
context.getDrawable(R.drawable.remote_input_view_text_bg).mutate();
mBackgroundColor = new ColorDrawable();
mBackground = new LayerDrawable(new Drawable[] {mBackgroundColor, mTextBackground});
mBackground = new ColorDrawable();
}
void setSupportedMimeTypes(@Nullable Collection<String> mimeTypes) {
@@ -741,6 +824,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
listener = mOnReceiveContentListener;
}
setOnReceiveContentListener(types, listener);
mSupportedMimes.clear();
mSupportedMimes.addAll(mimeTypes);
}
private void hideIme() {
@@ -899,36 +984,15 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
ContentInfo uriItems = split.first;
ContentInfo remainingItems = split.second;
if (uriItems != null) {
ClipData clip = uriItems.getClip();
ClipDescription description = clip.getDescription();
if (clip.getItemCount() > 1
|| description.getMimeTypeCount() < 1
|| remainingItems != null) {
// Direct-reply in notifications currently only supports only one uri item
// at a time and requires the MIME type to be set.
Log.w(TAG, "Invalid payload: " + payload);
return payload;
}
Uri contentUri = clip.getItemAt(0).getUri();
String mimeType = description.getMimeType(0);
Intent dataIntent =
mRemoteInputView.prepareRemoteInputFromData(mimeType, contentUri);
// We can release the uri permissions granted to us as soon as we've created the
// grant for the target app in the call above.
payload.releasePermissions();
mRemoteInputView.sendRemoteInput(dataIntent);
mRemoteInputView.setAttachment(uriItems);
}
return remainingItems;
}
protected void setAllColors(int backgroundColor, int editBackgroundColor,
int accentColor, int textColor, int hintTextColor) {
setBackgroundColor(backgroundColor);
mBackgroundColor.setColor(backgroundColor);
mTextBackground.setColor(editBackgroundColor);
int stroke = getContext().getResources()
.getDimensionPixelSize(R.dimen.remote_input_view_text_stroke);
mTextBackground.setStroke(stroke, accentColor);
setBackgroundColor(editBackgroundColor);
mBackground.setColor(editBackgroundColor);
setTextColor(textColor);
setHintTextColor(hintTextColor);
getTextCursorDrawable().setColorFilter(accentColor, PorterDuff.Mode.SRC_IN);