1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.toast;
18 
19 import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
20 import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
21 
22 import android.animation.Animator;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.UserIdInt;
26 import android.app.Application;
27 import android.content.Context;
28 import android.content.pm.ApplicationInfo;
29 import android.content.pm.PackageManager;
30 import android.content.pm.PackageManager.NameNotFoundException;
31 import android.content.res.Configuration;
32 import android.content.res.Resources;
33 import android.graphics.drawable.Drawable;
34 import android.os.Build;
35 import android.os.UserHandle;
36 import android.util.IconDrawableFactory;
37 import android.util.Log;
38 import android.view.LayoutInflater;
39 import android.view.View;
40 import android.widget.ImageView;
41 import android.widget.TextView;
42 
43 import com.android.internal.R;
44 import com.android.systemui.plugins.ToastPlugin;
45 
46 /**
47  * SystemUI TextToast that can be customized by ToastPlugins. Should never instantiate this class
48  * directly. Instead, use {@link ToastFactory#createToast}.
49  */
50 public class SystemUIToast implements ToastPlugin.Toast {
51     static final String TAG = "SystemUIToast";
52     final Context mContext;
53     final CharSequence mText;
54     final ToastPlugin.Toast mPluginToast;
55 
56     private final String mPackageName;
57     @UserIdInt private final int mUserId;
58     private final LayoutInflater mLayoutInflater;
59 
60     final int mDefaultX = 0;
61     final int mDefaultHorizontalMargin = 0;
62     final int mDefaultVerticalMargin = 0;
63 
64     private int mDefaultY;
65     private int mDefaultGravity;
66 
67     @NonNull private final View mToastView;
68     @Nullable private final Animator mInAnimator;
69     @Nullable private final Animator mOutAnimator;
70 
SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text, String packageName, int userId, int orientation)71     SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
72             String packageName, int userId, int orientation) {
73         this(layoutInflater, context, text, null, packageName, userId,
74                 orientation);
75     }
76 
SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text, ToastPlugin.Toast pluginToast, String packageName, @UserIdInt int userId, int orientation)77     SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
78             ToastPlugin.Toast pluginToast, String packageName, @UserIdInt int userId,
79             int orientation) {
80         mLayoutInflater = layoutInflater;
81         mContext = context;
82         mText = text;
83         mPluginToast = pluginToast;
84         mPackageName = packageName;
85         mUserId = userId;
86         mToastView = inflateToastView();
87         mInAnimator = createInAnimator();
88         mOutAnimator = createOutAnimator();
89 
90         onOrientationChange(orientation);
91     }
92 
93     @Override
94     @NonNull
getGravity()95     public Integer getGravity() {
96         if (isPluginToast() && mPluginToast.getGravity() != null) {
97             return mPluginToast.getGravity();
98         }
99         return mDefaultGravity;
100     }
101 
102     @Override
103     @NonNull
getXOffset()104     public Integer getXOffset() {
105         if (isPluginToast() && mPluginToast.getXOffset() != null) {
106             return mPluginToast.getXOffset();
107         }
108         return mDefaultX;
109     }
110 
111     @Override
112     @NonNull
getYOffset()113     public Integer getYOffset() {
114         if (isPluginToast() && mPluginToast.getYOffset() != null) {
115             return mPluginToast.getYOffset();
116         }
117         return mDefaultY;
118     }
119 
120     @Override
121     @NonNull
getHorizontalMargin()122     public Integer getHorizontalMargin() {
123         if (isPluginToast() && mPluginToast.getHorizontalMargin() != null) {
124             return mPluginToast.getHorizontalMargin();
125         }
126         return mDefaultHorizontalMargin;
127     }
128 
129     @Override
130     @NonNull
getVerticalMargin()131     public Integer getVerticalMargin() {
132         if (isPluginToast() && mPluginToast.getVerticalMargin() != null) {
133             return mPluginToast.getVerticalMargin();
134         }
135         return mDefaultVerticalMargin;
136     }
137 
138     @Override
139     @NonNull
getView()140     public View getView() {
141         return mToastView;
142     }
143 
144     @Override
145     @Nullable
getInAnimation()146     public Animator getInAnimation() {
147         return mInAnimator;
148     }
149 
150     @Override
151     @Nullable
getOutAnimation()152     public Animator getOutAnimation() {
153         return mOutAnimator;
154     }
155 
156     /**
157      * Whether this toast has a custom animation.
158      */
hasCustomAnimation()159     public boolean hasCustomAnimation() {
160         return getInAnimation() != null || getOutAnimation() != null;
161     }
162 
isPluginToast()163     private boolean isPluginToast() {
164         return mPluginToast != null;
165     }
166 
inflateToastView()167     private View inflateToastView() {
168         if (isPluginToast() && mPluginToast.getView() != null) {
169             return mPluginToast.getView();
170         }
171 
172         final View toastView = mLayoutInflater.inflate(
173                     com.android.systemui.res.R.layout.text_toast, null);
174         final TextView textView = toastView.findViewById(com.android.systemui.res.R.id.text);
175         final ImageView iconView = toastView.findViewById(com.android.systemui.res.R.id.icon);
176         textView.setText(mText);
177 
178         ApplicationInfo appInfo = null;
179         try {
180             appInfo = mContext.getPackageManager()
181                     .getApplicationInfoAsUser(mPackageName, 0, mUserId);
182         } catch (PackageManager.NameNotFoundException e) {
183             Log.e(TAG, "Package name not found package=" + mPackageName
184                     + " user=" + mUserId);
185         }
186 
187         if (appInfo != null && appInfo.targetSdkVersion < Build.VERSION_CODES.S) {
188             // no two-line limit
189             textView.setMaxLines(Integer.MAX_VALUE);
190 
191             // no app icon
192             toastView.findViewById(com.android.systemui.res.R.id.icon).setVisibility(View.GONE);
193         } else {
194             Drawable icon = getBadgedIcon(mContext, mPackageName, mUserId);
195             if (icon == null) {
196                 iconView.setVisibility(View.GONE);
197             } else {
198                 iconView.setImageDrawable(icon);
199                 if (appInfo == null) {
200                     Log.d(TAG, "No appInfo for pkg=" + mPackageName + " usr=" + mUserId);
201                 } else if (appInfo.labelRes != 0) {
202                     try {
203                         Resources res = mContext.getPackageManager().getResourcesForApplication(
204                                 appInfo,
205                                 new Configuration(mContext.getResources().getConfiguration()));
206                         iconView.setContentDescription(res.getString(appInfo.labelRes));
207                     } catch (PackageManager.NameNotFoundException e) {
208                         Log.d(TAG, "Cannot find application resources for icon label.");
209                     }
210                 }
211             }
212         }
213         return toastView;
214     }
215 
216     /**
217      * Called on orientation changes to update parameters associated with the toast placement.
218      */
onOrientationChange(int orientation)219     public void onOrientationChange(int orientation) {
220         if (mPluginToast != null) {
221             mPluginToast.onOrientationChange(orientation);
222         }
223 
224         mDefaultY = mContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
225         mDefaultGravity =
226                 mContext.getResources().getInteger(R.integer.config_toastDefaultGravity);
227     }
228 
createInAnimator()229     private Animator createInAnimator() {
230         if (isPluginToast() && mPluginToast.getInAnimation() != null) {
231             return mPluginToast.getInAnimation();
232         }
233 
234         return ToastDefaultAnimation.Companion.toastIn(getView());
235     }
236 
createOutAnimator()237     private Animator createOutAnimator() {
238         if (isPluginToast() && mPluginToast.getOutAnimation() != null) {
239             return mPluginToast.getOutAnimation();
240         }
241         return ToastDefaultAnimation.Companion.toastOut(getView());
242     }
243 
244     /**
245      * Get badged app icon if necessary, similar as used in the Settings UI.
246      * @return The icon to use
247      */
getBadgedIcon(@onNull Context context, String packageName, int userId)248     public static Drawable getBadgedIcon(@NonNull Context context, String packageName,
249             int userId) {
250         if (!(context.getApplicationContext() instanceof Application)) {
251             return null;
252         }
253 
254         try {
255             final PackageManager packageManager = context.getPackageManager();
256             final ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser(
257                     packageName,
258                     PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA),
259                     userId);
260             if (appInfo == null || !showApplicationIcon(appInfo, packageManager)) {
261                 return null;
262             }
263 
264             IconDrawableFactory iconFactory = IconDrawableFactory.newInstance(context);
265             return iconFactory.getBadgedIcon(appInfo, UserHandle.getUserId(appInfo.uid));
266         } catch (NameNotFoundException e) {
267             Log.e(TAG, "Couldn't find application info for packageName=" + packageName
268                     + " userId=" + userId);
269             return null;
270         }
271     }
272 
showApplicationIcon(ApplicationInfo appInfo, PackageManager packageManager)273     private static boolean showApplicationIcon(ApplicationInfo appInfo,
274             PackageManager packageManager) {
275         if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP | FLAG_SYSTEM)) {
276             return packageManager.getLaunchIntentForPackage(appInfo.packageName) != null;
277         }
278         return true;
279     }
280 
hasFlag(int flags, int flag)281     private static boolean hasFlag(int flags, int flag) {
282         return (flags & flag) != 0;
283     }
284 }
285