/* * Copyright (C) 2020 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.toast; import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; import android.animation.Animator; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.Application; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.UserHandle; import android.util.IconDrawableFactory; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import com.android.internal.R; import com.android.systemui.plugins.ToastPlugin; /** * SystemUI TextToast that can be customized by ToastPlugins. Should never instantiate this class * directly. Instead, use {@link ToastFactory#createToast}. */ public class SystemUIToast implements ToastPlugin.Toast { static final String TAG = "SystemUIToast"; final Context mContext; final CharSequence mText; final ToastPlugin.Toast mPluginToast; private final String mPackageName; @UserIdInt private final int mUserId; private final LayoutInflater mLayoutInflater; final int mDefaultX = 0; final int mDefaultHorizontalMargin = 0; final int mDefaultVerticalMargin = 0; private int mDefaultY; private int mDefaultGravity; @NonNull private final View mToastView; @Nullable private final Animator mInAnimator; @Nullable private final Animator mOutAnimator; SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text, String packageName, int userId, int orientation) { this(layoutInflater, context, text, null, packageName, userId, orientation); } SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text, ToastPlugin.Toast pluginToast, String packageName, @UserIdInt int userId, int orientation) { mLayoutInflater = layoutInflater; mContext = context; mText = text; mPluginToast = pluginToast; mPackageName = packageName; mUserId = userId; mToastView = inflateToastView(); mInAnimator = createInAnimator(); mOutAnimator = createOutAnimator(); onOrientationChange(orientation); } @Override @NonNull public Integer getGravity() { if (isPluginToast() && mPluginToast.getGravity() != null) { return mPluginToast.getGravity(); } return mDefaultGravity; } @Override @NonNull public Integer getXOffset() { if (isPluginToast() && mPluginToast.getXOffset() != null) { return mPluginToast.getXOffset(); } return mDefaultX; } @Override @NonNull public Integer getYOffset() { if (isPluginToast() && mPluginToast.getYOffset() != null) { return mPluginToast.getYOffset(); } return mDefaultY; } @Override @NonNull public Integer getHorizontalMargin() { if (isPluginToast() && mPluginToast.getHorizontalMargin() != null) { return mPluginToast.getHorizontalMargin(); } return mDefaultHorizontalMargin; } @Override @NonNull public Integer getVerticalMargin() { if (isPluginToast() && mPluginToast.getVerticalMargin() != null) { return mPluginToast.getVerticalMargin(); } return mDefaultVerticalMargin; } @Override @NonNull public View getView() { return mToastView; } @Override @Nullable public Animator getInAnimation() { return mInAnimator; } @Override @Nullable public Animator getOutAnimation() { return mOutAnimator; } /** * Whether this toast has a custom animation. */ public boolean hasCustomAnimation() { return getInAnimation() != null || getOutAnimation() != null; } private boolean isPluginToast() { return mPluginToast != null; } private View inflateToastView() { if (isPluginToast() && mPluginToast.getView() != null) { return mPluginToast.getView(); } final View toastView = mLayoutInflater.inflate( com.android.systemui.R.layout.text_toast, null); final TextView textView = toastView.findViewById(com.android.systemui.R.id.text); final ImageView iconView = toastView.findViewById(com.android.systemui.R.id.icon); textView.setText(mText); ApplicationInfo appInfo = null; try { appInfo = mContext.getPackageManager() .getApplicationInfoAsUser(mPackageName, 0, mUserId); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Package name not found package=" + mPackageName + " user=" + mUserId); } if (appInfo != null && appInfo.targetSdkVersion < Build.VERSION_CODES.S) { // no two-line limit textView.setMaxLines(Integer.MAX_VALUE); // no app icon toastView.findViewById(com.android.systemui.R.id.icon).setVisibility(View.GONE); } else { Drawable icon = getBadgedIcon(mContext, mPackageName, mUserId); if (icon == null) { iconView.setVisibility(View.GONE); } else { iconView.setImageDrawable(icon); if (appInfo == null) { Log.d(TAG, "No appInfo for pkg=" + mPackageName + " usr=" + mUserId); } else if (appInfo.labelRes != 0) { try { Resources res = mContext.getPackageManager().getResourcesForApplication( appInfo, new Configuration(mContext.getResources().getConfiguration())); iconView.setContentDescription(res.getString(appInfo.labelRes)); } catch (PackageManager.NameNotFoundException e) { Log.d(TAG, "Cannot find application resources for icon label."); } } } } return toastView; } /** * Called on orientation changes to update parameters associated with the toast placement. */ public void onOrientationChange(int orientation) { if (mPluginToast != null) { mPluginToast.onOrientationChange(orientation); } mDefaultY = mContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset); mDefaultGravity = mContext.getResources().getInteger(R.integer.config_toastDefaultGravity); } private Animator createInAnimator() { if (isPluginToast() && mPluginToast.getInAnimation() != null) { return mPluginToast.getInAnimation(); } return ToastDefaultAnimation.Companion.toastIn(getView()); } private Animator createOutAnimator() { if (isPluginToast() && mPluginToast.getOutAnimation() != null) { return mPluginToast.getOutAnimation(); } return ToastDefaultAnimation.Companion.toastOut(getView()); } /** * Get badged app icon if necessary, similar as used in the Settings UI. * @return The icon to use */ public static Drawable getBadgedIcon(@NonNull Context context, String packageName, int userId) { if (!(context.getApplicationContext() instanceof Application)) { return null; } try { final PackageManager packageManager = context.getPackageManager(); final ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser( packageName, PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA), userId); if (appInfo == null || !showApplicationIcon(appInfo, packageManager)) { return null; } IconDrawableFactory iconFactory = IconDrawableFactory.newInstance(context); return iconFactory.getBadgedIcon(appInfo, UserHandle.getUserId(appInfo.uid)); } catch (NameNotFoundException e) { Log.e(TAG, "Couldn't find application info for packageName=" + packageName + " userId=" + userId); return null; } } private static boolean showApplicationIcon(ApplicationInfo appInfo, PackageManager packageManager) { if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP | FLAG_SYSTEM)) { return packageManager.getLaunchIntentForPackage(appInfo.packageName) != null; } return true; } private static boolean hasFlag(int flags, int flag) { return (flags & flag) != 0; } }