• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 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.applications;
18 
19 import android.app.Activity;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.PackageManager;
24 import android.os.Bundle;
25 import android.os.Environment;
26 import android.os.UserHandle;
27 import android.os.UserManager;
28 import android.preference.PreferenceFrameLayout;
29 import android.provider.Settings;
30 import android.util.ArraySet;
31 import android.util.Log;
32 import android.view.LayoutInflater;
33 import android.view.Menu;
34 import android.view.MenuInflater;
35 import android.view.MenuItem;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.view.animation.AnimationUtils;
39 import android.widget.AbsListView;
40 import android.widget.AdapterView;
41 import android.widget.AdapterView.OnItemClickListener;
42 import android.widget.AdapterView.OnItemSelectedListener;
43 import android.widget.ArrayAdapter;
44 import android.widget.BaseAdapter;
45 import android.widget.Filter;
46 import android.widget.Filterable;
47 import android.widget.FrameLayout;
48 import android.widget.ListView;
49 import android.widget.Spinner;
50 
51 import com.android.internal.logging.MetricsLogger;
52 import com.android.settings.AppHeader;
53 import com.android.settings.HelpUtils;
54 import com.android.settings.InstrumentedFragment;
55 import com.android.settings.R;
56 import com.android.settings.Settings.AllApplicationsActivity;
57 import com.android.settings.Settings.DomainsURLsAppListActivity;
58 import com.android.settings.Settings.HighPowerApplicationsActivity;
59 import com.android.settings.Settings.NotificationAppListActivity;
60 import com.android.settings.Settings.StorageUseActivity;
61 import com.android.settings.Settings.UsageAccessSettingsActivity;
62 import com.android.settings.Settings.OverlaySettingsActivity;
63 import com.android.settings.Settings.WriteSettingsActivity;
64 import com.android.settings.SettingsActivity;
65 import com.android.settings.Utils;
66 import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
67 import com.android.settings.applications.AppStateUsageBridge.UsageState;
68 import com.android.settings.fuelgauge.HighPowerDetail;
69 import com.android.settings.fuelgauge.PowerWhitelistBackend;
70 import com.android.settings.notification.AppNotificationSettings;
71 import com.android.settings.notification.NotificationBackend;
72 import com.android.settings.notification.NotificationBackend.AppRow;
73 import com.android.settingslib.applications.ApplicationsState;
74 import com.android.settingslib.applications.ApplicationsState.AppEntry;
75 import com.android.settingslib.applications.ApplicationsState.AppFilter;
76 import com.android.settingslib.applications.ApplicationsState.CompoundFilter;
77 import com.android.settingslib.applications.ApplicationsState.VolumeFilter;
78 
79 import java.util.ArrayList;
80 import java.util.Collections;
81 import java.util.Comparator;
82 
83 /**
84  * Activity to pick an application that will be used to display installation information and
85  * options to uninstall/delete user data for system applications. This activity
86  * can be launched through Settings or via the ACTION_MANAGE_PACKAGE_STORAGE
87  * intent.
88  */
89 public class ManageApplications extends InstrumentedFragment
90         implements OnItemClickListener, OnItemSelectedListener {
91 
92     static final String TAG = "ManageApplications";
93     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
94 
95     // Intent extras.
96     public static final String EXTRA_CLASSNAME = "classname";
97     // Used for storage only.
98     public static final String EXTRA_VOLUME_UUID = "volumeUuid";
99     public static final String EXTRA_VOLUME_NAME = "volumeName";
100 
101     private static final String EXTRA_SORT_ORDER = "sortOrder";
102     private static final String EXTRA_SHOW_SYSTEM = "showSystem";
103     private static final String EXTRA_HAS_ENTRIES = "hasEntries";
104 
105     // attributes used as keys when passing values to InstalledAppDetails activity
106     public static final String APP_CHG = "chg";
107 
108     // constant value that can be used to check return code from sub activity.
109     private static final int INSTALLED_APP_DETAILS = 1;
110     private static final int ADVANCED_SETTINGS = 2;
111 
112     public static final int SIZE_TOTAL = 0;
113     public static final int SIZE_INTERNAL = 1;
114     public static final int SIZE_EXTERNAL = 2;
115 
116     // Filter options used for displayed list of applications
117     // The order which they appear is the order they will show when spinner is present.
118     public static final int FILTER_APPS_POWER_WHITELIST         = 0;
119     public static final int FILTER_APPS_POWER_WHITELIST_ALL     = 1;
120     public static final int FILTER_APPS_ALL                     = 2;
121     public static final int FILTER_APPS_ENABLED                 = 3;
122     public static final int FILTER_APPS_DISABLED                = 4;
123     public static final int FILTER_APPS_BLOCKED                 = 5;
124     public static final int FILTER_APPS_PRIORITY                = 6;
125     public static final int FILTER_APPS_NO_PEEKING              = 7;
126     public static final int FILTER_APPS_SENSITIVE               = 8;
127     public static final int FILTER_APPS_PERSONAL                = 9;
128     public static final int FILTER_APPS_WORK                    = 10;
129     public static final int FILTER_APPS_WITH_DOMAIN_URLS        = 11;
130     public static final int FILTER_APPS_USAGE_ACCESS            = 12;
131     public static final int FILTER_APPS_WITH_OVERLAY            = 13;
132     public static final int FILTER_APPS_WRITE_SETTINGS          = 14;
133 
134     // This is the string labels for the filter modes above, the order must be kept in sync.
135     public static final int[] FILTER_LABELS = new int[] {
136         R.string.high_power_filter_on, // High power whitelist, on
137         R.string.filter_all_apps,      // All apps label, but personal filter (for high power);
138         R.string.filter_all_apps,      // All apps
139         R.string.filter_enabled_apps,  // Enabled
140         R.string.filter_apps_disabled, // Disabled
141         R.string.filter_notif_blocked_apps,   // Blocked Notifications
142         R.string.filter_notif_priority_apps,  // Priority Notifications
143         R.string.filter_notif_no_peeking,     // No peeking Notifications
144         R.string.filter_notif_sensitive_apps, // Sensitive Notifications
145         R.string.filter_personal_apps, // Personal
146         R.string.filter_work_apps,     // Work
147         R.string.filter_with_domain_urls_apps,     // Domain URLs
148         R.string.filter_all_apps,      // Usage access screen, never displayed
149         R.string.filter_overlay_apps,   // Apps with overlay permission
150         R.string.filter_write_settings_apps,   // Apps that can write system settings
151     };
152     // This is the actual mapping to filters from FILTER_ constants above, the order must
153     // be kept in sync.
154     public static final AppFilter[] FILTERS = new AppFilter[] {
155         new CompoundFilter(AppStatePowerBridge.FILTER_POWER_WHITELISTED,
156                 ApplicationsState.FILTER_ALL_ENABLED),     // High power whitelist, on
157         new CompoundFilter(ApplicationsState.FILTER_PERSONAL_WITHOUT_DISABLED_UNTIL_USED,
158                 ApplicationsState.FILTER_ALL_ENABLED),     // All apps label, but personal filter
159         ApplicationsState.FILTER_EVERYTHING,  // All apps
160         ApplicationsState.FILTER_ALL_ENABLED, // Enabled
161         ApplicationsState.FILTER_DISABLED,    // Disabled
162         AppStateNotificationBridge.FILTER_APP_NOTIFICATION_BLOCKED,   // Blocked Notifications
163         AppStateNotificationBridge.FILTER_APP_NOTIFICATION_PRIORITY,  // Priority Notifications
164         AppStateNotificationBridge.FILTER_APP_NOTIFICATION_NO_PEEK,   // No peeking Notifications
165         AppStateNotificationBridge.FILTER_APP_NOTIFICATION_SENSITIVE, // Sensitive Notifications
166         ApplicationsState.FILTER_PERSONAL,    // Personal
167         ApplicationsState.FILTER_WORK,        // Work
168         ApplicationsState.FILTER_WITH_DOMAIN_URLS,   // Apps with Domain URLs
169         AppStateUsageBridge.FILTER_APP_USAGE, // Apps with Domain URLs
170         AppStateOverlayBridge.FILTER_SYSTEM_ALERT_WINDOW,   // Apps that can draw overlays
171         AppStateWriteSettingsBridge.FILTER_WRITE_SETTINGS,  // Apps that can write system settings
172     };
173 
174     // sort order
175     private int mSortOrder = R.id.sort_order_alpha;
176 
177     // whether showing system apps.
178     private boolean mShowSystem;
179 
180     private ApplicationsState mApplicationsState;
181 
182     public int mListType;
183     public int mFilter;
184 
185     public ApplicationsAdapter mApplications;
186 
187     private View mLoadingContainer;
188 
189     private View mListContainer;
190 
191     // ListView used to display list
192     private ListView mListView;
193 
194     // Size resource used for packages whose size computation failed for some reason
195     CharSequence mInvalidSizeStr;
196 
197     // layout inflater object used to inflate views
198     private LayoutInflater mInflater;
199 
200     private String mCurrentPkgName;
201     private int mCurrentUid;
202     private boolean mFinishAfterDialog;
203 
204     private Menu mOptionsMenu;
205 
206     public static final int LIST_TYPE_MAIN         = 0;
207     public static final int LIST_TYPE_NOTIFICATION = 1;
208     public static final int LIST_TYPE_DOMAINS_URLS = 2;
209     public static final int LIST_TYPE_STORAGE      = 3;
210     public static final int LIST_TYPE_USAGE_ACCESS = 4;
211     public static final int LIST_TYPE_HIGH_POWER   = 5;
212     public static final int LIST_TYPE_OVERLAY      = 6;
213     public static final int LIST_TYPE_WRITE_SETTINGS = 7;
214 
215     private View mRootView;
216 
217     private View mSpinnerHeader;
218     private Spinner mFilterSpinner;
219     private FilterSpinnerAdapter mFilterAdapter;
220     private NotificationBackend mNotifBackend;
221     private ResetAppsHelper mResetAppsHelper;
222     private String mVolumeUuid;
223     private String mVolumeName;
224 
225     @Override
onCreate(Bundle savedInstanceState)226     public void onCreate(Bundle savedInstanceState) {
227         super.onCreate(savedInstanceState);
228         setHasOptionsMenu(true);
229         mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication());
230 
231         Intent intent = getActivity().getIntent();
232         Bundle args = getArguments();
233         String className = args != null ? args.getString(EXTRA_CLASSNAME) : null;
234         if (className == null) {
235             className = intent.getComponent().getClassName();
236         }
237         if (className.equals(AllApplicationsActivity.class.getName())) {
238             mShowSystem = true;
239         } else if (className.equals(NotificationAppListActivity.class.getName())) {
240             mListType = LIST_TYPE_NOTIFICATION;
241             mNotifBackend = new NotificationBackend();
242         } else if (className.equals(DomainsURLsAppListActivity.class.getName())) {
243             mListType = LIST_TYPE_DOMAINS_URLS;
244         } else if (className.equals(StorageUseActivity.class.getName())) {
245             if (args != null && args.containsKey(EXTRA_VOLUME_UUID)) {
246                 mVolumeUuid = args.getString(EXTRA_VOLUME_UUID);
247                 mVolumeName = args.getString(EXTRA_VOLUME_NAME);
248                 mListType = LIST_TYPE_STORAGE;
249             } else {
250                 // No volume selected, display a normal list, sorted by size.
251                 mListType = LIST_TYPE_MAIN;
252             }
253             mSortOrder = R.id.sort_order_size;
254         } else if (className.equals(UsageAccessSettingsActivity.class.getName())) {
255             mListType = LIST_TYPE_USAGE_ACCESS;
256             getActivity().getActionBar().setTitle(R.string.usage_access_title);
257         } else if (className.equals(HighPowerApplicationsActivity.class.getName())) {
258             mListType = LIST_TYPE_HIGH_POWER;
259             // Default to showing system.
260             mShowSystem = true;
261         } else if (className.equals(OverlaySettingsActivity.class.getName())) {
262             mListType = LIST_TYPE_OVERLAY;
263             getActivity().getActionBar().setTitle(R.string.system_alert_window_access_title);
264         } else if (className.equals(WriteSettingsActivity.class.getName())) {
265             mListType = LIST_TYPE_WRITE_SETTINGS;
266             getActivity().getActionBar().setTitle(R.string.write_settings_title);
267         } else {
268             mListType = LIST_TYPE_MAIN;
269         }
270         mFilter = getDefaultFilter();
271 
272         if (savedInstanceState != null) {
273             mSortOrder = savedInstanceState.getInt(EXTRA_SORT_ORDER, mSortOrder);
274             mShowSystem = savedInstanceState.getBoolean(EXTRA_SHOW_SYSTEM, mShowSystem);
275         }
276 
277         mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value);
278 
279         mResetAppsHelper = new ResetAppsHelper(getActivity());
280     }
281 
282 
283     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)284     public View onCreateView(LayoutInflater inflater, ViewGroup container,
285             Bundle savedInstanceState) {
286         // initialize the inflater
287         mInflater = inflater;
288 
289         mRootView = inflater.inflate(R.layout.manage_applications_apps, null);
290         mLoadingContainer = mRootView.findViewById(R.id.loading_container);
291         mLoadingContainer.setVisibility(View.VISIBLE);
292         mListContainer = mRootView.findViewById(R.id.list_container);
293         if (mListContainer != null) {
294             // Create adapter and list view here
295             View emptyView = mListContainer.findViewById(com.android.internal.R.id.empty);
296             ListView lv = (ListView) mListContainer.findViewById(android.R.id.list);
297             if (emptyView != null) {
298                 lv.setEmptyView(emptyView);
299             }
300             lv.setOnItemClickListener(this);
301             lv.setSaveEnabled(true);
302             lv.setItemsCanFocus(true);
303             lv.setTextFilterEnabled(true);
304             mListView = lv;
305             mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter);
306             if (savedInstanceState != null) {
307                 mApplications.mHasReceivedLoadEntries =
308                         savedInstanceState.getBoolean(EXTRA_HAS_ENTRIES, false);
309             }
310             mListView.setAdapter(mApplications);
311             mListView.setRecyclerListener(mApplications);
312 
313             Utils.prepareCustomPreferencesList(container, mRootView, mListView, false);
314         }
315 
316         // We have to do this now because PreferenceFrameLayout looks at it
317         // only when the view is added.
318         if (container instanceof PreferenceFrameLayout) {
319             ((PreferenceFrameLayout.LayoutParams) mRootView.getLayoutParams()).removeBorders = true;
320         }
321 
322         createHeader();
323 
324         mResetAppsHelper.onRestoreInstanceState(savedInstanceState);
325 
326         return mRootView;
327     }
328 
createHeader()329     private void createHeader() {
330         Activity activity = getActivity();
331         FrameLayout pinnedHeader = (FrameLayout) mRootView.findViewById(R.id.pinned_header);
332         mSpinnerHeader = (ViewGroup) activity.getLayoutInflater()
333                 .inflate(R.layout.apps_filter_spinner, pinnedHeader, false);
334         mFilterSpinner = (Spinner) mSpinnerHeader.findViewById(R.id.filter_spinner);
335         mFilterAdapter = new FilterSpinnerAdapter(this);
336         mFilterSpinner.setAdapter(mFilterAdapter);
337         mFilterSpinner.setOnItemSelectedListener(this);
338         pinnedHeader.addView(mSpinnerHeader, 0);
339 
340         mFilterAdapter.enableFilter(getDefaultFilter());
341         if (mListType == LIST_TYPE_MAIN || mListType == LIST_TYPE_NOTIFICATION) {
342             if (UserManager.get(getActivity()).getUserProfiles().size() > 1) {
343                 mFilterAdapter.enableFilter(FILTER_APPS_PERSONAL);
344                 mFilterAdapter.enableFilter(FILTER_APPS_WORK);
345             }
346         }
347         if (mListType == LIST_TYPE_NOTIFICATION) {
348             mFilterAdapter.enableFilter(FILTER_APPS_BLOCKED);
349             mFilterAdapter.enableFilter(FILTER_APPS_PRIORITY);
350             mFilterAdapter.enableFilter(FILTER_APPS_SENSITIVE);
351             mFilterAdapter.enableFilter(FILTER_APPS_NO_PEEKING);
352         }
353         if (mListType == LIST_TYPE_HIGH_POWER) {
354             mFilterAdapter.enableFilter(FILTER_APPS_POWER_WHITELIST_ALL);
355         }
356         if (mListType == LIST_TYPE_STORAGE) {
357             mApplications.setOverrideFilter(new VolumeFilter(mVolumeUuid));
358         }
359     }
360 
361     @Override
onViewCreated(View view, Bundle savedInstanceState)362     public void onViewCreated(View view, Bundle savedInstanceState) {
363         super.onViewCreated(view, savedInstanceState);
364         if (mListType == LIST_TYPE_STORAGE) {
365             FrameLayout pinnedHeader = (FrameLayout) mRootView.findViewById(R.id.pinned_header);
366             AppHeader.createAppHeader(getActivity(), null, mVolumeName, null, pinnedHeader);
367         }
368     }
369 
getDefaultFilter()370     private int getDefaultFilter() {
371         switch (mListType) {
372             case LIST_TYPE_DOMAINS_URLS:
373                 return FILTER_APPS_WITH_DOMAIN_URLS;
374             case LIST_TYPE_USAGE_ACCESS:
375                 return FILTER_APPS_USAGE_ACCESS;
376             case LIST_TYPE_HIGH_POWER:
377                 return FILTER_APPS_POWER_WHITELIST;
378             case LIST_TYPE_OVERLAY:
379                 return FILTER_APPS_WITH_OVERLAY;
380             case LIST_TYPE_WRITE_SETTINGS:
381                 return FILTER_APPS_WRITE_SETTINGS;
382             default:
383                 return FILTER_APPS_ALL;
384         }
385     }
386 
387     @Override
getMetricsCategory()388     protected int getMetricsCategory() {
389         switch (mListType) {
390             case LIST_TYPE_MAIN:
391                 return MetricsLogger.MANAGE_APPLICATIONS;
392             case LIST_TYPE_NOTIFICATION:
393                 return MetricsLogger.MANAGE_APPLICATIONS_NOTIFICATIONS;
394             case LIST_TYPE_DOMAINS_URLS:
395                 return MetricsLogger.MANAGE_DOMAIN_URLS;
396             case LIST_TYPE_STORAGE:
397                 return MetricsLogger.APPLICATIONS_STORAGE_APPS;
398             case LIST_TYPE_USAGE_ACCESS:
399                 return MetricsLogger.USAGE_ACCESS;
400             case LIST_TYPE_HIGH_POWER:
401                 return MetricsLogger.APPLICATIONS_HIGH_POWER_APPS;
402             case LIST_TYPE_OVERLAY:
403                 return MetricsLogger.SYSTEM_ALERT_WINDOW_APPS;
404             case LIST_TYPE_WRITE_SETTINGS:
405                 return MetricsLogger.SYSTEM_ALERT_WINDOW_APPS;
406             default:
407                 return MetricsLogger.VIEW_UNKNOWN;
408         }
409     }
410 
411     @Override
onResume()412     public void onResume() {
413         super.onResume();
414         updateView();
415         updateOptionsMenu();
416         if (mApplications != null) {
417             mApplications.resume(mSortOrder);
418             mApplications.updateLoading();
419         }
420     }
421 
422     @Override
onSaveInstanceState(Bundle outState)423     public void onSaveInstanceState(Bundle outState) {
424         super.onSaveInstanceState(outState);
425         mResetAppsHelper.onSaveInstanceState(outState);
426         outState.putInt(EXTRA_SORT_ORDER, mSortOrder);
427         outState.putBoolean(EXTRA_SHOW_SYSTEM, mShowSystem);
428         outState.putBoolean(EXTRA_HAS_ENTRIES, mApplications.mHasReceivedLoadEntries);
429     }
430 
431     @Override
onPause()432     public void onPause() {
433         super.onPause();
434         if (mApplications != null) {
435             mApplications.pause();
436         }
437     }
438 
439     @Override
onStop()440     public void onStop() {
441         super.onStop();
442         mResetAppsHelper.stop();
443     }
444 
445     @Override
onDestroyView()446     public void onDestroyView() {
447         super.onDestroyView();
448 
449         if (mApplications != null) {
450             mApplications.release();
451         }
452         mRootView = null;
453     }
454 
455     @Override
onActivityResult(int requestCode, int resultCode, Intent data)456     public void onActivityResult(int requestCode, int resultCode, Intent data) {
457         if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) {
458             if (mListType == LIST_TYPE_NOTIFICATION) {
459                 mApplications.mExtraInfoBridge.forceUpdate(mCurrentPkgName, mCurrentUid);
460             } else if (mListType == LIST_TYPE_HIGH_POWER || mListType == LIST_TYPE_OVERLAY
461                     || mListType == LIST_TYPE_WRITE_SETTINGS) {
462                 if (mFinishAfterDialog) {
463                     getActivity().onBackPressed();
464                 } else {
465                     mApplications.mExtraInfoBridge.forceUpdate(mCurrentPkgName, mCurrentUid);
466                 }
467             } else {
468                 mApplicationsState.requestSize(mCurrentPkgName, UserHandle.getUserId(mCurrentUid));
469             }
470         }
471     }
472 
473     // utility method used to start sub activity
startApplicationDetailsActivity()474     private void startApplicationDetailsActivity() {
475         switch (mListType) {
476             case LIST_TYPE_NOTIFICATION:
477                 startAppInfoFragment(AppNotificationSettings.class,
478                         R.string.app_notifications_title);
479                 break;
480             case LIST_TYPE_DOMAINS_URLS:
481                 startAppInfoFragment(AppLaunchSettings.class, R.string.auto_launch_label);
482                 break;
483             case LIST_TYPE_USAGE_ACCESS:
484                 startAppInfoFragment(UsageAccessDetails.class, R.string.usage_access);
485                 break;
486             case LIST_TYPE_STORAGE:
487                 startAppInfoFragment(AppStorageSettings.class, R.string.storage_settings);
488                 break;
489             case LIST_TYPE_HIGH_POWER:
490                 HighPowerDetail.show(this, mCurrentPkgName, INSTALLED_APP_DETAILS,
491                         mFinishAfterDialog);
492                 break;
493             case LIST_TYPE_OVERLAY:
494                 startAppInfoFragment(DrawOverlayDetails.class, R.string.overlay_settings);
495                 break;
496             case LIST_TYPE_WRITE_SETTINGS:
497                 startAppInfoFragment(WriteSettingsDetails.class, R.string.write_system_settings);
498                 break;
499             // TODO: Figure out if there is a way where we can spin up the profile's settings
500             // process ahead of time, to avoid a long load of data when user clicks on a managed app.
501             // Maybe when they load the list of apps that contains managed profile apps.
502             default:
503                 startAppInfoFragment(InstalledAppDetails.class, R.string.application_info_label);
504                 break;
505         }
506     }
507 
startAppInfoFragment(Class<?> fragment, int titleRes)508     private void startAppInfoFragment(Class<?> fragment, int titleRes) {
509         AppInfoBase.startAppInfoFragment(fragment, titleRes, mCurrentPkgName, mCurrentUid, this,
510                 INSTALLED_APP_DETAILS);
511     }
512 
513     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)514     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
515         if (mListType == LIST_TYPE_DOMAINS_URLS) {
516             return;
517         }
518         HelpUtils.prepareHelpMenuItem(getActivity(), menu, mListType == LIST_TYPE_MAIN
519                 ? R.string.help_uri_apps : R.string.help_uri_notifications, getClass().getName());
520         mOptionsMenu = menu;
521         inflater.inflate(R.menu.manage_apps, menu);
522         updateOptionsMenu();
523     }
524 
525     @Override
onPrepareOptionsMenu(Menu menu)526     public void onPrepareOptionsMenu(Menu menu) {
527         updateOptionsMenu();
528     }
529 
530     @Override
onDestroyOptionsMenu()531     public void onDestroyOptionsMenu() {
532         mOptionsMenu = null;
533     }
534 
updateOptionsMenu()535     void updateOptionsMenu() {
536         if (mOptionsMenu == null) {
537             return;
538         }
539         mOptionsMenu.findItem(R.id.advanced).setVisible(mListType == LIST_TYPE_MAIN);
540 
541         mOptionsMenu.findItem(R.id.sort_order_alpha).setVisible(mListType == LIST_TYPE_STORAGE
542                 && mSortOrder != R.id.sort_order_alpha);
543         mOptionsMenu.findItem(R.id.sort_order_size).setVisible(mListType == LIST_TYPE_STORAGE
544                 && mSortOrder != R.id.sort_order_size);
545 
546         mOptionsMenu.findItem(R.id.show_system).setVisible(!mShowSystem
547                 && mListType != LIST_TYPE_HIGH_POWER);
548         mOptionsMenu.findItem(R.id.hide_system).setVisible(mShowSystem
549                 && mListType != LIST_TYPE_HIGH_POWER);
550     }
551 
552     @Override
onOptionsItemSelected(MenuItem item)553     public boolean onOptionsItemSelected(MenuItem item) {
554         int menuId = item.getItemId();
555         switch(item.getItemId()) {
556             case R.id.sort_order_alpha:
557             case R.id.sort_order_size:
558                 mSortOrder = menuId;
559                 if (mApplications != null) {
560                     mApplications.rebuild(mSortOrder);
561                 }
562                 break;
563             case R.id.show_system:
564             case R.id.hide_system:
565                 mShowSystem = !mShowSystem;
566                 mApplications.rebuild(false);
567                 break;
568             case R.id.reset_app_preferences:
569                 mResetAppsHelper.buildResetDialog();
570                 return true;
571             case R.id.advanced:
572                 ((SettingsActivity) getActivity()).startPreferencePanel(
573                         AdvancedAppSettings.class.getName(), null, R.string.configure_apps,
574                         null, this, ADVANCED_SETTINGS);
575                 return true;
576             default:
577                 // Handle the home button
578                 return false;
579         }
580         updateOptionsMenu();
581         return true;
582     }
583 
584     @Override
onItemClick(AdapterView<?> parent, View view, int position, long id)585     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
586         if (mApplications != null && mApplications.getCount() > position) {
587             ApplicationsState.AppEntry entry = mApplications.getAppEntry(position);
588             mCurrentPkgName = entry.info.packageName;
589             mCurrentUid = entry.info.uid;
590             startApplicationDetailsActivity();
591         }
592     }
593 
594     @Override
onItemSelected(AdapterView<?> parent, View view, int position, long id)595     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
596         mFilter = mFilterAdapter.getFilter(position);
597         mApplications.setFilter(mFilter);
598         if (DEBUG) Log.d(TAG, "Selecting filter " + mFilter);
599     }
600 
601     @Override
onNothingSelected(AdapterView<?> parent)602     public void onNothingSelected(AdapterView<?> parent) {
603     }
604 
updateView()605     public void updateView() {
606         updateOptionsMenu();
607         final Activity host = getActivity();
608         if (host != null) {
609             host.invalidateOptionsMenu();
610         }
611     }
612 
setHasDisabled(boolean hasDisabledApps)613     public void setHasDisabled(boolean hasDisabledApps) {
614         if (mListType == LIST_TYPE_HIGH_POWER) {
615             return;
616         }
617         mFilterAdapter.setFilterEnabled(FILTER_APPS_ENABLED, hasDisabledApps);
618         mFilterAdapter.setFilterEnabled(FILTER_APPS_DISABLED, hasDisabledApps);
619     }
620 
621     static class FilterSpinnerAdapter extends ArrayAdapter<CharSequence> {
622 
623         private final ManageApplications mManageApplications;
624 
625         // Use ArrayAdapter for view logic, but have our own list for managing
626         // the options available.
627         private final ArrayList<Integer> mFilterOptions = new ArrayList<>();
628 
FilterSpinnerAdapter(ManageApplications manageApplications)629         public FilterSpinnerAdapter(ManageApplications manageApplications) {
630             super(manageApplications.getActivity(), R.layout.filter_spinner_item);
631             setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
632             mManageApplications = manageApplications;
633         }
634 
getFilter(int position)635         public int getFilter(int position) {
636             return mFilterOptions.get(position);
637         }
638 
setFilterEnabled(int filter, boolean enabled)639         public void setFilterEnabled(int filter, boolean enabled) {
640             if (enabled) {
641                 enableFilter(filter);
642             } else {
643                 disableFilter(filter);
644             }
645         }
646 
enableFilter(int filter)647         public void enableFilter(int filter) {
648             if (mFilterOptions.contains(filter)) return;
649             if (DEBUG) Log.d(TAG, "Enabling filter " + filter);
650             mFilterOptions.add(filter);
651             Collections.sort(mFilterOptions);
652             mManageApplications.mSpinnerHeader.setVisibility(
653                     mFilterOptions.size() > 1 ? View.VISIBLE : View.GONE);
654             notifyDataSetChanged();
655             if (mFilterOptions.size() == 1) {
656                 if (DEBUG) Log.d(TAG, "Auto selecting filter " + filter);
657                 mManageApplications.mFilterSpinner.setSelection(0);
658                 mManageApplications.onItemSelected(null, null, 0, 0);
659             }
660         }
661 
disableFilter(int filter)662         public void disableFilter(int filter) {
663             if (!mFilterOptions.remove((Integer) filter)) {
664                 return;
665             }
666             if (DEBUG) Log.d(TAG, "Disabling filter " + filter);
667             Collections.sort(mFilterOptions);
668             mManageApplications.mSpinnerHeader.setVisibility(
669                     mFilterOptions.size() > 1 ? View.VISIBLE : View.GONE);
670             notifyDataSetChanged();
671             if (mManageApplications.mFilter == filter) {
672                 if (mFilterOptions.size() > 0) {
673                     if (DEBUG) Log.d(TAG, "Auto selecting filter " + mFilterOptions.get(0));
674                     mManageApplications.mFilterSpinner.setSelection(0);
675                     mManageApplications.onItemSelected(null, null, 0, 0);
676                 }
677             }
678         }
679 
680         @Override
getCount()681         public int getCount() {
682             return mFilterOptions.size();
683         }
684 
685         @Override
getItem(int position)686         public CharSequence getItem(int position) {
687             return getFilterString(mFilterOptions.get(position));
688         }
689 
getFilterString(int filter)690         private CharSequence getFilterString(int filter) {
691             return mManageApplications.getString(FILTER_LABELS[filter]);
692         }
693 
694     }
695 
696     /*
697      * Custom adapter implementation for the ListView
698      * This adapter maintains a map for each displayed application and its properties
699      * An index value on each AppInfo object indicates the correct position or index
700      * in the list. If the list gets updated dynamically when the user is viewing the list of
701      * applications, we need to return the correct index of position. This is done by mapping
702      * the getId methods via the package name into the internal maps and indices.
703      * The order of applications in the list is mirrored in mAppLocalList
704      */
705     static class ApplicationsAdapter extends BaseAdapter implements Filterable,
706             ApplicationsState.Callbacks, AppStateBaseBridge.Callback,
707             AbsListView.RecyclerListener {
708         private final ApplicationsState mState;
709         private final ApplicationsState.Session mSession;
710         private final ManageApplications mManageApplications;
711         private final Context mContext;
712         private final ArrayList<View> mActive = new ArrayList<View>();
713         private final AppStateBaseBridge mExtraInfoBridge;
714         private int mFilterMode;
715         private ArrayList<ApplicationsState.AppEntry> mBaseEntries;
716         private ArrayList<ApplicationsState.AppEntry> mEntries;
717         private boolean mResumed;
718         private int mLastSortMode=-1;
719         private int mWhichSize = SIZE_TOTAL;
720         CharSequence mCurFilterPrefix;
721         private PackageManager mPm;
722         private AppFilter mOverrideFilter;
723         private boolean mHasReceivedLoadEntries;
724         private boolean mHasReceivedBridgeCallback;
725 
726         private Filter mFilter = new Filter() {
727             @Override
728             protected FilterResults performFiltering(CharSequence constraint) {
729                 ArrayList<ApplicationsState.AppEntry> entries
730                         = applyPrefixFilter(constraint, mBaseEntries);
731                 FilterResults fr = new FilterResults();
732                 fr.values = entries;
733                 fr.count = entries.size();
734                 return fr;
735             }
736 
737             @Override
738             @SuppressWarnings("unchecked")
739             protected void publishResults(CharSequence constraint, FilterResults results) {
740                 mCurFilterPrefix = constraint;
741                 mEntries = (ArrayList<ApplicationsState.AppEntry>) results.values;
742                 notifyDataSetChanged();
743             }
744         };
745 
ApplicationsAdapter(ApplicationsState state, ManageApplications manageApplications, int filterMode)746         public ApplicationsAdapter(ApplicationsState state, ManageApplications manageApplications,
747                 int filterMode) {
748             mState = state;
749             mSession = state.newSession(this);
750             mManageApplications = manageApplications;
751             mContext = manageApplications.getActivity();
752             mPm = mContext.getPackageManager();
753             mFilterMode = filterMode;
754             if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) {
755                 mExtraInfoBridge = new AppStateNotificationBridge(mContext.getPackageManager(),
756                         mState, this, manageApplications.mNotifBackend);
757             } else if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
758                 mExtraInfoBridge = new AppStateUsageBridge(mContext, mState, this);
759             } else if (mManageApplications.mListType == LIST_TYPE_HIGH_POWER) {
760                 mExtraInfoBridge = new AppStatePowerBridge(mState, this);
761             } else if (mManageApplications.mListType == LIST_TYPE_OVERLAY) {
762                 mExtraInfoBridge = new AppStateOverlayBridge(mContext, mState, this);
763             } else if (mManageApplications.mListType == LIST_TYPE_WRITE_SETTINGS) {
764                 mExtraInfoBridge = new AppStateWriteSettingsBridge(mContext, mState, this);
765             } else {
766                 mExtraInfoBridge = null;
767             }
768         }
769 
setOverrideFilter(AppFilter overrideFilter)770         public void setOverrideFilter(AppFilter overrideFilter) {
771             mOverrideFilter = overrideFilter;
772             rebuild(true);
773         }
774 
setFilter(int filter)775         public void setFilter(int filter) {
776             mFilterMode = filter;
777             rebuild(true);
778         }
779 
resume(int sort)780         public void resume(int sort) {
781             if (DEBUG) Log.i(TAG, "Resume!  mResumed=" + mResumed);
782             if (!mResumed) {
783                 mResumed = true;
784                 mSession.resume();
785                 mLastSortMode = sort;
786                 if (mExtraInfoBridge != null) {
787                     mExtraInfoBridge.resume();
788                 }
789                 rebuild(true);
790             } else {
791                 rebuild(sort);
792             }
793         }
794 
pause()795         public void pause() {
796             if (mResumed) {
797                 mResumed = false;
798                 mSession.pause();
799                 if (mExtraInfoBridge != null) {
800                     mExtraInfoBridge.pause();
801                 }
802             }
803         }
804 
release()805         public void release() {
806             mSession.release();
807             if (mExtraInfoBridge != null) {
808                 mExtraInfoBridge.release();
809             }
810         }
811 
rebuild(int sort)812         public void rebuild(int sort) {
813             if (sort == mLastSortMode) {
814                 return;
815             }
816             mLastSortMode = sort;
817             rebuild(true);
818         }
819 
rebuild(boolean eraseold)820         public void rebuild(boolean eraseold) {
821             if (!mHasReceivedLoadEntries
822                     && (mExtraInfoBridge == null || mHasReceivedBridgeCallback)) {
823                 // Don't rebuild the list until all the app entries are loaded.
824                 return;
825             }
826             if (DEBUG) Log.i(TAG, "Rebuilding app list...");
827             ApplicationsState.AppFilter filterObj;
828             Comparator<AppEntry> comparatorObj;
829             boolean emulated = Environment.isExternalStorageEmulated();
830             if (emulated) {
831                 mWhichSize = SIZE_TOTAL;
832             } else {
833                 mWhichSize = SIZE_INTERNAL;
834             }
835             filterObj = FILTERS[mFilterMode];
836             if (mOverrideFilter != null) {
837                 filterObj = mOverrideFilter;
838             }
839             if (!mManageApplications.mShowSystem) {
840                 filterObj = new CompoundFilter(filterObj,
841                         ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER);
842             }
843             switch (mLastSortMode) {
844                 case R.id.sort_order_size:
845                     switch (mWhichSize) {
846                         case SIZE_INTERNAL:
847                             comparatorObj = ApplicationsState.INTERNAL_SIZE_COMPARATOR;
848                             break;
849                         case SIZE_EXTERNAL:
850                             comparatorObj = ApplicationsState.EXTERNAL_SIZE_COMPARATOR;
851                             break;
852                         default:
853                             comparatorObj = ApplicationsState.SIZE_COMPARATOR;
854                             break;
855                     }
856                     break;
857                 default:
858                     comparatorObj = ApplicationsState.ALPHA_COMPARATOR;
859                     break;
860             }
861             ArrayList<ApplicationsState.AppEntry> entries
862                     = mSession.rebuild(filterObj, comparatorObj);
863             if (entries == null && !eraseold) {
864                 // Don't have new list yet, but can continue using the old one.
865                 return;
866             }
867             mBaseEntries = entries;
868             if (mBaseEntries != null) {
869                 mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries);
870             } else {
871                 mEntries = null;
872             }
873             notifyDataSetChanged();
874 
875             if (mSession.getAllApps().size() != 0
876                     && mManageApplications.mListContainer.getVisibility() != View.VISIBLE) {
877                 Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
878                         mManageApplications.mListContainer, true, true);
879             }
880             if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
881                 // No enabled or disabled filters for usage access.
882                 return;
883             }
884 
885             mManageApplications.setHasDisabled(mState.haveDisabledApps());
886         }
887 
updateLoading()888         private void updateLoading() {
889             Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
890                     mManageApplications.mListContainer,
891                     mHasReceivedLoadEntries && mSession.getAllApps().size() != 0, false);
892         }
893 
applyPrefixFilter(CharSequence prefix, ArrayList<ApplicationsState.AppEntry> origEntries)894         ArrayList<ApplicationsState.AppEntry> applyPrefixFilter(CharSequence prefix,
895                 ArrayList<ApplicationsState.AppEntry> origEntries) {
896             if (prefix == null || prefix.length() == 0) {
897                 return origEntries;
898             } else {
899                 String prefixStr = ApplicationsState.normalize(prefix.toString());
900                 final String spacePrefixStr = " " + prefixStr;
901                 ArrayList<ApplicationsState.AppEntry> newEntries
902                         = new ArrayList<ApplicationsState.AppEntry>();
903                 for (int i=0; i<origEntries.size(); i++) {
904                     ApplicationsState.AppEntry entry = origEntries.get(i);
905                     String nlabel = entry.getNormalizedLabel();
906                     if (nlabel.startsWith(prefixStr) || nlabel.indexOf(spacePrefixStr) != -1) {
907                         newEntries.add(entry);
908                     }
909                 }
910                 return newEntries;
911             }
912         }
913 
914         @Override
onExtraInfoUpdated()915         public void onExtraInfoUpdated() {
916             mHasReceivedBridgeCallback = true;
917             rebuild(false);
918         }
919 
920         @Override
onRunningStateChanged(boolean running)921         public void onRunningStateChanged(boolean running) {
922             mManageApplications.getActivity().setProgressBarIndeterminateVisibility(running);
923         }
924 
925         @Override
onRebuildComplete(ArrayList<AppEntry> apps)926         public void onRebuildComplete(ArrayList<AppEntry> apps) {
927             if (mManageApplications.mLoadingContainer.getVisibility() == View.VISIBLE) {
928                 mManageApplications.mLoadingContainer.startAnimation(AnimationUtils.loadAnimation(
929                         mContext, android.R.anim.fade_out));
930                 mManageApplications.mListContainer.startAnimation(AnimationUtils.loadAnimation(
931                         mContext, android.R.anim.fade_in));
932             }
933             mManageApplications.mListContainer.setVisibility(View.VISIBLE);
934             mManageApplications.mLoadingContainer.setVisibility(View.GONE);
935             mBaseEntries = apps;
936             mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries);
937             notifyDataSetChanged();
938         }
939 
940         @Override
onPackageListChanged()941         public void onPackageListChanged() {
942             rebuild(false);
943         }
944 
945         @Override
onPackageIconChanged()946         public void onPackageIconChanged() {
947             // We ensure icons are loaded when their item is displayed, so
948             // don't care about icons loaded in the background.
949         }
950 
951         @Override
onLoadEntriesCompleted()952         public void onLoadEntriesCompleted() {
953             mHasReceivedLoadEntries = true;
954         }
955 
956         @Override
onPackageSizeChanged(String packageName)957         public void onPackageSizeChanged(String packageName) {
958             for (int i=0; i<mActive.size(); i++) {
959                 AppViewHolder holder = (AppViewHolder)mActive.get(i).getTag();
960                 if (holder.entry.info.packageName.equals(packageName)) {
961                     synchronized (holder.entry) {
962                         updateSummary(holder);
963                     }
964                     if (holder.entry.info.packageName.equals(mManageApplications.mCurrentPkgName)
965                             && mLastSortMode == R.id.sort_order_size) {
966                         // We got the size information for the last app the
967                         // user viewed, and are sorting by size...  they may
968                         // have cleared data, so we immediately want to resort
969                         // the list with the new size to reflect it to the user.
970                         rebuild(false);
971                     }
972                     return;
973                 }
974             }
975         }
976 
977         @Override
onLauncherInfoChanged()978         public void onLauncherInfoChanged() {
979             if (!mManageApplications.mShowSystem) {
980                 rebuild(false);
981             }
982         }
983 
984         @Override
onAllSizesComputed()985         public void onAllSizesComputed() {
986             if (mLastSortMode == R.id.sort_order_size) {
987                 rebuild(false);
988             }
989         }
990 
getCount()991         public int getCount() {
992             return mEntries != null ? mEntries.size() : 0;
993         }
994 
getItem(int position)995         public Object getItem(int position) {
996             return mEntries.get(position);
997         }
998 
getAppEntry(int position)999         public ApplicationsState.AppEntry getAppEntry(int position) {
1000             return mEntries.get(position);
1001         }
1002 
getItemId(int position)1003         public long getItemId(int position) {
1004             return mEntries.get(position).id;
1005         }
1006 
1007         @Override
areAllItemsEnabled()1008         public boolean areAllItemsEnabled() {
1009             return false;
1010         }
1011 
1012         @Override
isEnabled(int position)1013         public boolean isEnabled(int position) {
1014             if (mManageApplications.mListType != LIST_TYPE_HIGH_POWER) {
1015                 return true;
1016             }
1017             ApplicationsState.AppEntry entry = mEntries.get(position);
1018             return !PowerWhitelistBackend.getInstance().isSysWhitelisted(entry.info.packageName);
1019         }
1020 
getView(int position, View convertView, ViewGroup parent)1021         public View getView(int position, View convertView, ViewGroup parent) {
1022             // A ViewHolder keeps references to children views to avoid unnecessary calls
1023             // to findViewById() on each row.
1024             AppViewHolder holder = AppViewHolder.createOrRecycle(mManageApplications.mInflater,
1025                     convertView);
1026             convertView = holder.rootView;
1027 
1028             // Bind the data efficiently with the holder
1029             ApplicationsState.AppEntry entry = mEntries.get(position);
1030             synchronized (entry) {
1031                 holder.entry = entry;
1032                 if (entry.label != null) {
1033                     holder.appName.setText(entry.label);
1034                 }
1035                 mState.ensureIcon(entry);
1036                 if (entry.icon != null) {
1037                     holder.appIcon.setImageDrawable(entry.icon);
1038                 }
1039                 updateSummary(holder);
1040                 if ((entry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
1041                     holder.disabled.setVisibility(View.VISIBLE);
1042                     holder.disabled.setText(R.string.not_installed);
1043                 } else if (!entry.info.enabled) {
1044                     holder.disabled.setVisibility(View.VISIBLE);
1045                     holder.disabled.setText(R.string.disabled);
1046                 } else {
1047                     holder.disabled.setVisibility(View.GONE);
1048                 }
1049             }
1050             mActive.remove(convertView);
1051             mActive.add(convertView);
1052             convertView.setEnabled(isEnabled(position));
1053             return convertView;
1054         }
1055 
updateSummary(AppViewHolder holder)1056         private void updateSummary(AppViewHolder holder) {
1057             switch (mManageApplications.mListType) {
1058                 case LIST_TYPE_NOTIFICATION:
1059                     if (holder.entry.extraInfo != null) {
1060                         holder.summary.setText(InstalledAppDetails.getNotificationSummary(
1061                                 (AppRow) holder.entry.extraInfo, mContext));
1062                     } else {
1063                         holder.summary.setText(null);
1064                     }
1065                     break;
1066 
1067                 case LIST_TYPE_DOMAINS_URLS:
1068                     holder.summary.setText(getDomainsSummary(holder.entry.info.packageName));
1069                     break;
1070 
1071                 case LIST_TYPE_USAGE_ACCESS:
1072                     if (holder.entry.extraInfo != null) {
1073                         holder.summary.setText((new UsageState((PermissionState)holder.entry
1074                                 .extraInfo)).isPermissible() ? R.string.switch_on_text :
1075                                 R.string.switch_off_text);
1076                     } else {
1077                         holder.summary.setText(null);
1078                     }
1079                     break;
1080 
1081                 case LIST_TYPE_HIGH_POWER:
1082                     holder.summary.setText(HighPowerDetail.getSummary(mContext, holder.entry));
1083                     break;
1084 
1085                 case LIST_TYPE_OVERLAY:
1086                     holder.summary.setText(DrawOverlayDetails.getSummary(mContext, holder.entry));
1087                     break;
1088 
1089                 case LIST_TYPE_WRITE_SETTINGS:
1090                     holder.summary.setText(WriteSettingsDetails.getSummary(mContext,
1091                             holder.entry));
1092                     break;
1093 
1094                 default:
1095                     holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize);
1096                     break;
1097             }
1098         }
1099 
1100         @Override
getFilter()1101         public Filter getFilter() {
1102             return mFilter;
1103         }
1104 
1105         @Override
onMovedToScrapHeap(View view)1106         public void onMovedToScrapHeap(View view) {
1107             mActive.remove(view);
1108         }
1109 
getDomainsSummary(String packageName)1110         private CharSequence getDomainsSummary(String packageName) {
1111             // If the user has explicitly said "no" for this package, that's the
1112             // string we should show.
1113             int domainStatus = mPm.getIntentVerificationStatus(packageName, UserHandle.myUserId());
1114             if (domainStatus == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
1115                 return mContext.getString(R.string.domain_urls_summary_none);
1116             }
1117             // Otherwise, ask package manager for the domains for this package,
1118             // and show the first one (or none if there aren't any).
1119             ArraySet<String> result = Utils.getHandledDomains(mPm, packageName);
1120             if (result.size() == 0) {
1121                 return mContext.getString(R.string.domain_urls_summary_none);
1122             } else if (result.size() == 1) {
1123                 return mContext.getString(R.string.domain_urls_summary_one, result.valueAt(0));
1124             } else {
1125                 return mContext.getString(R.string.domain_urls_summary_some, result.valueAt(0));
1126             }
1127         }
1128     }
1129 }
1130