• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.settings.notification.app;
18 
19 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
20 
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.animation.ValueAnimator;
24 import android.app.Notification;
25 import android.app.NotificationChannel;
26 import android.app.NotificationChannelGroup;
27 import android.app.NotificationManager;
28 import android.app.role.RoleManager;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.content.pm.ActivityInfo;
34 import android.content.pm.PackageInfo;
35 import android.content.pm.PackageManager;
36 import android.content.pm.PackageManager.NameNotFoundException;
37 import android.content.pm.ResolveInfo;
38 import android.content.pm.ShortcutInfo;
39 import android.graphics.drawable.Drawable;
40 import android.os.Bundle;
41 import android.os.UserHandle;
42 import android.provider.Settings;
43 import android.text.TextUtils;
44 import android.util.Log;
45 import android.view.View;
46 import android.view.ViewGroup;
47 import android.view.ViewTreeObserver;
48 import android.view.animation.DecelerateInterpolator;
49 import android.widget.Toast;
50 
51 import androidx.annotation.NonNull;
52 import androidx.preference.PreferenceScreen;
53 
54 import com.android.settings.R;
55 import com.android.settings.SettingsActivity;
56 import com.android.settings.applications.AppInfoBase;
57 import com.android.settings.dashboard.DashboardFragment;
58 import com.android.settings.notification.NotificationBackend;
59 import com.android.settingslib.RestrictedLockUtilsInternal;
60 import com.android.settingslib.notification.ConversationIconFactory;
61 
62 import java.util.ArrayList;
63 import java.util.List;
64 
65 abstract public class NotificationSettings extends DashboardFragment {
66     private static final String TAG = "NotifiSettingsBase";
67     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
68 
69     protected PackageManager mPm;
70     protected NotificationBackend mBackend = new NotificationBackend();
71     protected NotificationManager mNm;
72     protected RoleManager mRm;
73     protected Context mContext;
74 
75     protected int mUid;
76     protected int mUserId;
77     protected String mPkg;
78     protected PackageInfo mPkgInfo;
79     protected EnforcedAdmin mSuspendedAppsAdmin;
80     protected NotificationChannelGroup mChannelGroup;
81     protected NotificationChannel mChannel;
82     protected NotificationBackend.AppRow mAppRow;
83     protected Drawable mConversationDrawable;
84     protected ShortcutInfo mConversationInfo;
85     protected List<String> mPreferenceFilter;
86 
87     protected boolean mShowLegacyChannelConfig = false;
88     protected boolean mListeningToPackageRemove;
89 
90     protected List<NotificationPreferenceController> mControllers = new ArrayList<>();
91     protected DependentFieldListener mDependentFieldListener = new DependentFieldListener();
92 
93     protected Intent mIntent;
94     protected Bundle mArgs;
95 
96     private ViewGroup mLayoutView;
97     private static final int DURATION_ANIMATE_PANEL_EXPAND_MS = 250;
98 
99     private final ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener =
100             new ViewTreeObserver.OnGlobalLayoutListener() {
101                 @Override
102                 public void onGlobalLayout() {
103                     animateIn();
104                     if (mLayoutView != null) {
105                         mLayoutView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
106                     }
107                 }
108             };
109 
110     @Override
onAttach(Context context)111     public void onAttach(Context context) {
112         super.onAttach(context);
113         mContext = getActivity();
114         mIntent = getActivity().getIntent();
115         mArgs = getArguments();
116 
117         mPm = getPackageManager();
118         mNm = NotificationManager.from(mContext);
119         mRm = mContext.getSystemService(RoleManager.class);
120 
121         mPkg = mArgs != null && mArgs.containsKey(AppInfoBase.ARG_PACKAGE_NAME)
122                 ? mArgs.getString(AppInfoBase.ARG_PACKAGE_NAME)
123                 : mIntent.getStringExtra(Settings.EXTRA_APP_PACKAGE);
124         mUid = mArgs != null && mArgs.containsKey(AppInfoBase.ARG_PACKAGE_UID)
125                 ? mArgs.getInt(AppInfoBase.ARG_PACKAGE_UID)
126                 : mIntent.getIntExtra(Settings.EXTRA_APP_UID, -1);
127 
128         if (mUid < 0) {
129             try {
130                 mUid = mPm.getPackageUid(mPkg, 0);
131             } catch (NameNotFoundException e) {
132             }
133         }
134 
135         mPkgInfo = findPackageInfo(mPkg, mUid);
136 
137         if (mPkgInfo != null) {
138             mUserId = UserHandle.getUserId(mUid);
139             mSuspendedAppsAdmin = RestrictedLockUtilsInternal.checkIfApplicationIsSuspended(
140                     mContext, mPkg, mUserId);
141 
142             loadChannel();
143             loadAppRow();
144             loadChannelGroup();
145             loadPreferencesFilter();
146             collectConfigActivities();
147 
148             if (use(HeaderPreferenceController.class) != null) {
149                 getSettingsLifecycle().addObserver(use(HeaderPreferenceController.class));
150             }
151             if (use(ConversationHeaderPreferenceController.class) != null) {
152                 getSettingsLifecycle().addObserver(
153                         use(ConversationHeaderPreferenceController.class));
154             }
155 
156             for (NotificationPreferenceController controller : mControllers) {
157                 controller.onResume(mAppRow, mChannel, mChannelGroup, null, null,
158                         mSuspendedAppsAdmin, mPreferenceFilter);
159             }
160         }
161     }
162 
163     @Override
onCreate(Bundle savedInstanceState)164     public void onCreate(Bundle savedInstanceState) {
165         super.onCreate(savedInstanceState);
166 
167         if (mIntent == null && mArgs == null) {
168             Log.w(TAG, "No intent");
169             toastAndFinish();
170             return;
171         }
172 
173         if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) {
174             Log.w(TAG, "Missing package or uid or packageinfo");
175             toastAndFinish();
176             return;
177         }
178 
179         startListeningToPackageRemove();
180     }
181 
182     @Override
onDestroy()183     public void onDestroy() {
184         stopListeningToPackageRemove();
185         super.onDestroy();
186     }
187 
188     @Override
onResume()189     public void onResume() {
190         super.onResume();
191         if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null || mAppRow == null) {
192             Log.w(TAG, "Missing package or uid or packageinfo");
193             finish();
194             return;
195         }
196         // Reload app, channel, etc onResume in case they've changed. A little wasteful if we've
197         // just done onAttach but better than making every preference controller reload all
198         // the data
199         loadAppRow();
200         if (mAppRow == null) {
201             Log.w(TAG, "Can't load package");
202             finish();
203             return;
204         }
205         loadChannel();
206         loadConversation();
207         loadChannelGroup();
208         loadPreferencesFilter();
209         collectConfigActivities();
210     }
211 
animatePanel()212     protected void animatePanel() {
213         if (mPreferenceFilter != null) {
214             mLayoutView = getActivity().findViewById(R.id.main_content);
215             mLayoutView.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
216         }
217     }
218 
219     /**
220      * Animate a Panel onto the screen.
221      * <p>
222      * Takes the entire panel and animates in from behind the navigation bar.
223      * <p>
224      * Relies on the Panel being having a fixed height to begin the animation.
225      */
animateIn()226     private void animateIn() {
227         final AnimatorSet animatorSet = buildAnimatorSet(mLayoutView,
228                 mLayoutView.getHeight() /* startY */, 0.0f /* endY */,
229                 0.0f /* startAlpha */, 1.0f /* endAlpha */,
230                 DURATION_ANIMATE_PANEL_EXPAND_MS);
231         final ValueAnimator animator = new ValueAnimator();
232         animator.setFloatValues(0.0f, 1.0f);
233         animatorSet.play(animator);
234         animatorSet.start();
235     }
236 
237     /**
238      * Build an {@link AnimatorSet} to animate the Panel, {@param parentView} in or out of the
239      * screen, based on the positional parameters {@param startY}, {@param endY}, the parameters
240      * for alpha changes {@param startAlpha}, {@param endAlpha}, and the {@param duration} in
241      * milliseconds.
242      */
243     @NonNull
buildAnimatorSet(@onNull View targetView, float startY, float endY, float startAlpha, float endAlpha, int duration)244     private static AnimatorSet buildAnimatorSet(@NonNull View targetView,
245             float startY, float endY,
246             float startAlpha, float endAlpha, int duration) {
247         final AnimatorSet animatorSet = new AnimatorSet();
248         animatorSet.setDuration(duration);
249         animatorSet.setInterpolator(new DecelerateInterpolator());
250         animatorSet.playTogether(
251                 ObjectAnimator.ofFloat(targetView, View.TRANSLATION_Y, startY, endY),
252                 ObjectAnimator.ofFloat(targetView, View.ALPHA, startAlpha, endAlpha));
253         return animatorSet;
254     }
255 
loadPreferencesFilter()256     private void loadPreferencesFilter() {
257         Intent intent = getActivity().getIntent();
258         mPreferenceFilter = intent != null
259                 ? intent.getStringArrayListExtra(Settings.EXTRA_CHANNEL_FILTER_LIST)
260                 : null;
261     }
262 
loadChannel()263     private void loadChannel() {
264         Intent intent = getActivity().getIntent();
265         String channelId = intent != null ? intent.getStringExtra(Settings.EXTRA_CHANNEL_ID) : null;
266         if (channelId == null && intent != null) {
267             Bundle args = intent.getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
268             channelId = args != null ? args.getString(Settings.EXTRA_CHANNEL_ID) : null;
269         }
270         String conversationId = intent != null
271                 ? intent.getStringExtra(Settings.EXTRA_CONVERSATION_ID) : null;
272         mChannel = mBackend.getChannel(mPkg, mUid, channelId, conversationId);
273     }
274 
loadConversation()275     private void loadConversation() {
276         if (mChannel == null || TextUtils.isEmpty(mChannel.getConversationId())
277                 || mChannel.isDemoted()) {
278             return;
279         }
280         mConversationInfo = mBackend.getConversationInfo(
281                 mContext, mPkg, mUid, mChannel.getConversationId());
282         if (mConversationInfo != null) {
283             mConversationDrawable = mBackend.getConversationDrawable(
284                     mContext, mConversationInfo, mAppRow.pkg, mAppRow.uid,
285                     mChannel.isImportantConversation());
286         }
287     }
288 
loadAppRow()289     private void loadAppRow() {
290         mAppRow = mBackend.loadAppRow(mContext, mPm, mRm, mPkgInfo);
291     }
292 
loadChannelGroup()293     private void loadChannelGroup() {
294         mShowLegacyChannelConfig = mBackend.onlyHasDefaultChannel(mAppRow.pkg, mAppRow.uid)
295                 || (mChannel != null
296                 && NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId()));
297 
298         if (mShowLegacyChannelConfig) {
299             mChannel = mBackend.getChannel(
300                     mAppRow.pkg, mAppRow.uid, NotificationChannel.DEFAULT_CHANNEL_ID, null);
301         }
302         if (mChannel != null && !TextUtils.isEmpty(mChannel.getGroup())) {
303             NotificationChannelGroup group = mBackend.getGroup(mPkg, mUid, mChannel.getGroup());
304             if (group != null) {
305                 mChannelGroup = group;
306             }
307         }
308     }
309 
toastAndFinish()310     protected void toastAndFinish() {
311         Toast.makeText(mContext, R.string.app_not_found_dlg_text, Toast.LENGTH_SHORT).show();
312         getActivity().finish();
313     }
314 
collectConfigActivities()315     protected void collectConfigActivities() {
316         Intent intent = new Intent(Intent.ACTION_MAIN)
317                 .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
318                 .setPackage(mAppRow.pkg);
319         final List<ResolveInfo> resolveInfos = mPm.queryIntentActivities(
320                 intent,
321                 0 //PackageManager.MATCH_DEFAULT_ONLY
322         );
323         if (DEBUG) {
324             Log.d(TAG, "Found " + resolveInfos.size() + " preference activities"
325                     + (resolveInfos.size() == 0 ? " ;_;" : ""));
326         }
327         for (ResolveInfo ri : resolveInfos) {
328             final ActivityInfo activityInfo = ri.activityInfo;
329             if (mAppRow.settingsIntent != null) {
330                 if (DEBUG) {
331                     Log.d(TAG, "Ignoring duplicate notification preference activity ("
332                             + activityInfo.name + ") for package "
333                             + activityInfo.packageName);
334                 }
335                 continue;
336             }
337             // TODO(78660939): This should actually start a new task
338             mAppRow.settingsIntent = intent
339                     .setPackage(null)
340                     .setClassName(activityInfo.packageName, activityInfo.name);
341             if (mChannel != null) {
342                 mAppRow.settingsIntent.putExtra(Notification.EXTRA_CHANNEL_ID, mChannel.getId());
343             }
344             if (mChannelGroup != null) {
345                 mAppRow.settingsIntent.putExtra(
346                         Notification.EXTRA_CHANNEL_GROUP_ID, mChannelGroup.getId());
347             }
348         }
349     }
350 
findPackageInfo(String pkg, int uid)351     private PackageInfo findPackageInfo(String pkg, int uid) {
352         if (pkg == null || uid < 0) {
353             return null;
354         }
355         final String[] packages = mPm.getPackagesForUid(uid);
356         if (packages != null && pkg != null) {
357             final int N = packages.length;
358             for (int i = 0; i < N; i++) {
359                 final String p = packages[i];
360                 if (pkg.equals(p)) {
361                     try {
362                         return mPm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
363                     } catch (NameNotFoundException e) {
364                         Log.w(TAG, "Failed to load package " + pkg, e);
365                     }
366                 }
367             }
368         }
369         return null;
370     }
371 
startListeningToPackageRemove()372     protected void startListeningToPackageRemove() {
373         if (mListeningToPackageRemove) {
374             return;
375         }
376         mListeningToPackageRemove = true;
377         final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
378         filter.addDataScheme("package");
379         getContext().registerReceiver(mPackageRemovedReceiver, filter);
380     }
381 
stopListeningToPackageRemove()382     protected void stopListeningToPackageRemove() {
383         if (!mListeningToPackageRemove) {
384             return;
385         }
386         mListeningToPackageRemove = false;
387         getContext().unregisterReceiver(mPackageRemovedReceiver);
388     }
389 
onPackageRemoved()390     protected void onPackageRemoved() {
391         getActivity().finishAndRemoveTask();
392     }
393 
394     protected final BroadcastReceiver mPackageRemovedReceiver = new BroadcastReceiver() {
395         @Override
396         public void onReceive(Context context, Intent intent) {
397             String packageName = intent.getData().getSchemeSpecificPart();
398             if (mPkgInfo == null || TextUtils.equals(mPkgInfo.packageName, packageName)) {
399                 if (DEBUG) {
400                     Log.d(TAG, "Package (" + packageName + ") removed. Removing"
401                             + "NotificationSettingsBase.");
402                 }
403                 onPackageRemoved();
404             }
405         }
406     };
407 
408     protected class DependentFieldListener {
onFieldValueChanged()409         protected void onFieldValueChanged() {
410             // Reload the conversation drawable, which shows some channel/conversation state
411             if (mConversationDrawable != null && mConversationDrawable
412                     instanceof ConversationIconFactory.ConversationIconDrawable) {
413                 ((ConversationIconFactory.ConversationIconDrawable) mConversationDrawable)
414                         .setImportant(mChannel.isImportantConversation());
415             }
416             final PreferenceScreen screen = getPreferenceScreen();
417             for (NotificationPreferenceController controller : mControllers) {
418                 controller.displayPreference(screen);
419             }
420             updatePreferenceStates();
421         }
422     }
423 }
424