Loading core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +4 −0 Original line number Diff line number Diff line Loading @@ -81,6 +81,10 @@ public class SystemUiSystemPropertiesFlags { /** Gating the logging of DND state change events. */ public static final Flag LOG_DND_STATE_EVENTS = releasedFlag("persist.sysui.notification.log_dnd_state_events"); /** Gating the holding of WakeLocks until NLSes are told about a new notification. */ public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION = devFlag("persist.sysui.notification.wake_lock_for_posting_notification"); } //// == End of flags. Everything below this line is the implementation. == //// Loading services/core/java/com/android/server/notification/NotificationManagerService.java +46 −24 Original line number Diff line number Diff line Loading @@ -119,6 +119,7 @@ import static android.service.notification.NotificationListenerService.TRIM_LIGH import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; Loading Loading @@ -223,6 +224,8 @@ import android.os.IInterface; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; Loading @@ -234,6 +237,7 @@ import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.VibrationEffect; import android.os.WorkSource; import android.permission.PermissionManager; import android.provider.DeviceConfig; import android.provider.Settings; Loading Loading @@ -559,6 +563,7 @@ public class NotificationManagerService extends SystemService { private PermissionHelper mPermissionHelper; private UsageStatsManagerInternal mUsageStatsManagerInternal; private TelecomManager mTelecomManager; private PowerManager mPowerManager; private PostNotificationTrackerFactory mPostNotificationTrackerFactory; final IBinder mForegroundToken = new Binder(); Loading Loading @@ -923,7 +928,7 @@ public class NotificationManagerService extends SystemService { if (oldFlags != flags) { summary.getSbn().getNotification().flags = flags; mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground, mPostNotificationTrackerFactory.newTracker())); mPostNotificationTrackerFactory.newTracker(null))); } } Loading Loading @@ -1457,7 +1462,7 @@ public class NotificationManagerService extends SystemService { // want to adjust the flag behaviour. mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r, true /* isAppForeground*/, mPostNotificationTrackerFactory.newTracker())); mPostNotificationTrackerFactory.newTracker(null))); } } } Loading Loading @@ -1488,7 +1493,7 @@ public class NotificationManagerService extends SystemService { mHandler.post( new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r, /* foreground= */ true, mPostNotificationTrackerFactory.newTracker())); mPostNotificationTrackerFactory.newTracker(null))); } } } Loading Loading @@ -2233,7 +2238,7 @@ public class NotificationManagerService extends SystemService { UsageStatsManagerInternal usageStatsManagerInternal, TelecomManager telecomManager, NotificationChannelLogger channelLogger, SystemUiSystemPropertiesFlags.FlagResolver flagResolver, PermissionManager permissionManager, PermissionManager permissionManager, PowerManager powerManager, PostNotificationTrackerFactory postNotificationTrackerFactory) { mHandler = handler; Resources resources = getContext().getResources(); Loading Loading @@ -2265,6 +2270,7 @@ public class NotificationManagerService extends SystemService { mDpm = dpm; mUm = userManager; mTelecomManager = telecomManager; mPowerManager = powerManager; mPostNotificationTrackerFactory = postNotificationTrackerFactory; mPlatformCompat = IPlatformCompat.Stub.asInterface( ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); Loading Loading @@ -2568,6 +2574,7 @@ public class NotificationManagerService extends SystemService { getContext().getSystemService(TelecomManager.class), new NotificationChannelLoggerImpl(), SystemUiSystemPropertiesFlags.getResolver(), getContext().getSystemService(PermissionManager.class), getContext().getSystemService(PowerManager.class), new PostNotificationTrackerFactory() {}); publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false, Loading Loading @@ -2684,7 +2691,7 @@ public class NotificationManagerService extends SystemService { final boolean isAppForeground = mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND; mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground, mPostNotificationTrackerFactory.newTracker())); mPostNotificationTrackerFactory.newTracker(null))); } } Loading Loading @@ -6577,7 +6584,7 @@ public class NotificationManagerService extends SystemService { void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int incomingUserId, boolean postSilently) { PostNotificationTracker tracker = mPostNotificationTrackerFactory.newTracker(); PostNotificationTracker tracker = acquireWakeLockForPost(pkg, callingUid); boolean enqueued = false; try { enqueued = enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, Loading @@ -6589,6 +6596,22 @@ public class NotificationManagerService extends SystemService { } } private PostNotificationTracker acquireWakeLockForPost(String pkg, int uid) { if (mFlagResolver.isEnabled(WAKE_LOCK_FOR_POSTING_NOTIFICATION)) { // The package probably doesn't have WAKE_LOCK permission and should not require it. return Binder.withCleanCallingIdentity(() -> { WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "NotificationManagerService:post:" + pkg); wakeLock.setWorkSource(new WorkSource(uid, pkg)); // TODO(b/275044361): Adjust to a more reasonable number when we have the data. wakeLock.acquire(30_000); return mPostNotificationTrackerFactory.newTracker(wakeLock); }); } else { return mPostNotificationTrackerFactory.newTracker(null); } } /** * @return True if we successfully processed the notification and handed off the task of * enqueueing it to a background thread; false otherwise. Loading Loading @@ -7106,7 +7129,7 @@ public class NotificationManagerService extends SystemService { mHandler.post( new NotificationManagerService.EnqueueNotificationRunnable( r.getUser().getIdentifier(), r, isAppForeground, mPostNotificationTrackerFactory.newTracker())); mPostNotificationTrackerFactory.newTracker(null))); } } } Loading Loading @@ -12168,20 +12191,20 @@ public class NotificationManagerService extends SystemService { } interface PostNotificationTrackerFactory { default PostNotificationTracker newTracker() { return new PostNotificationTracker(); default PostNotificationTracker newTracker(@Nullable WakeLock optionalWakelock) { return new PostNotificationTracker(optionalWakelock); } } static class PostNotificationTracker { @ElapsedRealtimeLong private final long mStartTime; @Nullable private NotificationRecordLogger.NotificationReported mReport; @Nullable private final WakeLock mWakeLock; private boolean mOngoing; @VisibleForTesting PostNotificationTracker() { // TODO(b/275044361): (Conditionally) receive a wakelock. PostNotificationTracker(@Nullable WakeLock wakeLock) { mStartTime = SystemClock.elapsedRealtime(); mWakeLock = wakeLock; mOngoing = true; if (DBG) { Slog.d(TAG, "PostNotification: Started"); Loading @@ -12199,9 +12222,8 @@ public class NotificationManagerService extends SystemService { } /** * Cancels the tracker (TODO(b/275044361): releasing the acquired WakeLock). Either * {@link #finish} or {@link #cancel} (exclusively) should be called on this object before * it's discarded. * Cancels the tracker (releasing the acquired WakeLock). Either {@link #finish} or * {@link #cancel} (exclusively) should be called on this object before it's discarded. */ void cancel() { if (!isOngoing()) { Loading @@ -12209,9 +12231,9 @@ public class NotificationManagerService extends SystemService { return; } mOngoing = false; // TODO(b/275044361): Release wakelock. if (mWakeLock != null) { Binder.withCleanCallingIdentity(() -> mWakeLock.release()); } if (DBG) { long elapsedTime = SystemClock.elapsedRealtime() - mStartTime; Slog.d(TAG, TextUtils.formatSimple("PostNotification: Abandoned after %d ms", Loading @@ -12220,9 +12242,9 @@ public class NotificationManagerService extends SystemService { } /** * Finishes the tracker (TODO(b/275044361): releasing the acquired WakeLock) and returns the * time elapsed since the operation started, in milliseconds. Either {@link #finish} or * {@link #cancel} (exclusively) should be called on this object before it's discarded. * Finishes the tracker (releasing the acquired WakeLock) and returns the time elapsed since * the operation started, in milliseconds. Either {@link #finish} or {@link #cancel} * (exclusively) should be called on this object before it's discarded. */ @DurationMillisLong long finish() { Loading @@ -12232,9 +12254,9 @@ public class NotificationManagerService extends SystemService { return elapsedTime; } mOngoing = false; // TODO(b/275044361): Release wakelock. if (mWakeLock != null) { Binder.withCleanCallingIdentity(() -> mWakeLock.release()); } if (DBG) { Slog.d(TAG, TextUtils.formatSimple("PostNotification: Finished in %d ms", elapsedTime)); services/tests/uiservicestests/AndroidManifest.xml +2 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" /> <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> <uses-permission android:name="android.permission.MANAGE_USERS" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" /> Loading @@ -36,6 +37,7 @@ <uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" /> <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> Loading services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +162 −24 File changed.Preview size limit exceeded, changes collapsed. Show changes services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java +2 −0 Original line number Diff line number Diff line Loading @@ -48,6 +48,7 @@ import android.content.Context; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.os.Looper; import android.os.PowerManager; import android.os.UserHandle; import android.os.UserManager; import android.permission.PermissionManager; Loading Loading @@ -169,6 +170,7 @@ public class RoleObserverTest extends UiServiceTestCase { mock(UsageStatsManagerInternal.class), mock(TelecomManager.class), mock(NotificationChannelLogger.class), new TestableFlagResolver(), mock(PermissionManager.class), mock(PowerManager.class), new NotificationManagerService.PostNotificationTrackerFactory() {}); } catch (SecurityException e) { if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { Loading Loading
core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +4 −0 Original line number Diff line number Diff line Loading @@ -81,6 +81,10 @@ public class SystemUiSystemPropertiesFlags { /** Gating the logging of DND state change events. */ public static final Flag LOG_DND_STATE_EVENTS = releasedFlag("persist.sysui.notification.log_dnd_state_events"); /** Gating the holding of WakeLocks until NLSes are told about a new notification. */ public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION = devFlag("persist.sysui.notification.wake_lock_for_posting_notification"); } //// == End of flags. Everything below this line is the implementation. == //// Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +46 −24 Original line number Diff line number Diff line Loading @@ -119,6 +119,7 @@ import static android.service.notification.NotificationListenerService.TRIM_LIGH import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; Loading Loading @@ -223,6 +224,8 @@ import android.os.IInterface; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; Loading @@ -234,6 +237,7 @@ import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.VibrationEffect; import android.os.WorkSource; import android.permission.PermissionManager; import android.provider.DeviceConfig; import android.provider.Settings; Loading Loading @@ -559,6 +563,7 @@ public class NotificationManagerService extends SystemService { private PermissionHelper mPermissionHelper; private UsageStatsManagerInternal mUsageStatsManagerInternal; private TelecomManager mTelecomManager; private PowerManager mPowerManager; private PostNotificationTrackerFactory mPostNotificationTrackerFactory; final IBinder mForegroundToken = new Binder(); Loading Loading @@ -923,7 +928,7 @@ public class NotificationManagerService extends SystemService { if (oldFlags != flags) { summary.getSbn().getNotification().flags = flags; mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground, mPostNotificationTrackerFactory.newTracker())); mPostNotificationTrackerFactory.newTracker(null))); } } Loading Loading @@ -1457,7 +1462,7 @@ public class NotificationManagerService extends SystemService { // want to adjust the flag behaviour. mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r, true /* isAppForeground*/, mPostNotificationTrackerFactory.newTracker())); mPostNotificationTrackerFactory.newTracker(null))); } } } Loading Loading @@ -1488,7 +1493,7 @@ public class NotificationManagerService extends SystemService { mHandler.post( new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r, /* foreground= */ true, mPostNotificationTrackerFactory.newTracker())); mPostNotificationTrackerFactory.newTracker(null))); } } } Loading Loading @@ -2233,7 +2238,7 @@ public class NotificationManagerService extends SystemService { UsageStatsManagerInternal usageStatsManagerInternal, TelecomManager telecomManager, NotificationChannelLogger channelLogger, SystemUiSystemPropertiesFlags.FlagResolver flagResolver, PermissionManager permissionManager, PermissionManager permissionManager, PowerManager powerManager, PostNotificationTrackerFactory postNotificationTrackerFactory) { mHandler = handler; Resources resources = getContext().getResources(); Loading Loading @@ -2265,6 +2270,7 @@ public class NotificationManagerService extends SystemService { mDpm = dpm; mUm = userManager; mTelecomManager = telecomManager; mPowerManager = powerManager; mPostNotificationTrackerFactory = postNotificationTrackerFactory; mPlatformCompat = IPlatformCompat.Stub.asInterface( ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); Loading Loading @@ -2568,6 +2574,7 @@ public class NotificationManagerService extends SystemService { getContext().getSystemService(TelecomManager.class), new NotificationChannelLoggerImpl(), SystemUiSystemPropertiesFlags.getResolver(), getContext().getSystemService(PermissionManager.class), getContext().getSystemService(PowerManager.class), new PostNotificationTrackerFactory() {}); publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false, Loading Loading @@ -2684,7 +2691,7 @@ public class NotificationManagerService extends SystemService { final boolean isAppForeground = mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND; mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground, mPostNotificationTrackerFactory.newTracker())); mPostNotificationTrackerFactory.newTracker(null))); } } Loading Loading @@ -6577,7 +6584,7 @@ public class NotificationManagerService extends SystemService { void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int incomingUserId, boolean postSilently) { PostNotificationTracker tracker = mPostNotificationTrackerFactory.newTracker(); PostNotificationTracker tracker = acquireWakeLockForPost(pkg, callingUid); boolean enqueued = false; try { enqueued = enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, Loading @@ -6589,6 +6596,22 @@ public class NotificationManagerService extends SystemService { } } private PostNotificationTracker acquireWakeLockForPost(String pkg, int uid) { if (mFlagResolver.isEnabled(WAKE_LOCK_FOR_POSTING_NOTIFICATION)) { // The package probably doesn't have WAKE_LOCK permission and should not require it. return Binder.withCleanCallingIdentity(() -> { WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "NotificationManagerService:post:" + pkg); wakeLock.setWorkSource(new WorkSource(uid, pkg)); // TODO(b/275044361): Adjust to a more reasonable number when we have the data. wakeLock.acquire(30_000); return mPostNotificationTrackerFactory.newTracker(wakeLock); }); } else { return mPostNotificationTrackerFactory.newTracker(null); } } /** * @return True if we successfully processed the notification and handed off the task of * enqueueing it to a background thread; false otherwise. Loading Loading @@ -7106,7 +7129,7 @@ public class NotificationManagerService extends SystemService { mHandler.post( new NotificationManagerService.EnqueueNotificationRunnable( r.getUser().getIdentifier(), r, isAppForeground, mPostNotificationTrackerFactory.newTracker())); mPostNotificationTrackerFactory.newTracker(null))); } } } Loading Loading @@ -12168,20 +12191,20 @@ public class NotificationManagerService extends SystemService { } interface PostNotificationTrackerFactory { default PostNotificationTracker newTracker() { return new PostNotificationTracker(); default PostNotificationTracker newTracker(@Nullable WakeLock optionalWakelock) { return new PostNotificationTracker(optionalWakelock); } } static class PostNotificationTracker { @ElapsedRealtimeLong private final long mStartTime; @Nullable private NotificationRecordLogger.NotificationReported mReport; @Nullable private final WakeLock mWakeLock; private boolean mOngoing; @VisibleForTesting PostNotificationTracker() { // TODO(b/275044361): (Conditionally) receive a wakelock. PostNotificationTracker(@Nullable WakeLock wakeLock) { mStartTime = SystemClock.elapsedRealtime(); mWakeLock = wakeLock; mOngoing = true; if (DBG) { Slog.d(TAG, "PostNotification: Started"); Loading @@ -12199,9 +12222,8 @@ public class NotificationManagerService extends SystemService { } /** * Cancels the tracker (TODO(b/275044361): releasing the acquired WakeLock). Either * {@link #finish} or {@link #cancel} (exclusively) should be called on this object before * it's discarded. * Cancels the tracker (releasing the acquired WakeLock). Either {@link #finish} or * {@link #cancel} (exclusively) should be called on this object before it's discarded. */ void cancel() { if (!isOngoing()) { Loading @@ -12209,9 +12231,9 @@ public class NotificationManagerService extends SystemService { return; } mOngoing = false; // TODO(b/275044361): Release wakelock. if (mWakeLock != null) { Binder.withCleanCallingIdentity(() -> mWakeLock.release()); } if (DBG) { long elapsedTime = SystemClock.elapsedRealtime() - mStartTime; Slog.d(TAG, TextUtils.formatSimple("PostNotification: Abandoned after %d ms", Loading @@ -12220,9 +12242,9 @@ public class NotificationManagerService extends SystemService { } /** * Finishes the tracker (TODO(b/275044361): releasing the acquired WakeLock) and returns the * time elapsed since the operation started, in milliseconds. Either {@link #finish} or * {@link #cancel} (exclusively) should be called on this object before it's discarded. * Finishes the tracker (releasing the acquired WakeLock) and returns the time elapsed since * the operation started, in milliseconds. Either {@link #finish} or {@link #cancel} * (exclusively) should be called on this object before it's discarded. */ @DurationMillisLong long finish() { Loading @@ -12232,9 +12254,9 @@ public class NotificationManagerService extends SystemService { return elapsedTime; } mOngoing = false; // TODO(b/275044361): Release wakelock. if (mWakeLock != null) { Binder.withCleanCallingIdentity(() -> mWakeLock.release()); } if (DBG) { Slog.d(TAG, TextUtils.formatSimple("PostNotification: Finished in %d ms", elapsedTime));
services/tests/uiservicestests/AndroidManifest.xml +2 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" /> <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> <uses-permission android:name="android.permission.MANAGE_USERS" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" /> Loading @@ -36,6 +37,7 @@ <uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" /> <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> Loading
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +162 −24 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java +2 −0 Original line number Diff line number Diff line Loading @@ -48,6 +48,7 @@ import android.content.Context; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.os.Looper; import android.os.PowerManager; import android.os.UserHandle; import android.os.UserManager; import android.permission.PermissionManager; Loading Loading @@ -169,6 +170,7 @@ public class RoleObserverTest extends UiServiceTestCase { mock(UsageStatsManagerInternal.class), mock(TelecomManager.class), mock(NotificationChannelLogger.class), new TestableFlagResolver(), mock(PermissionManager.class), mock(PowerManager.class), new NotificationManagerService.PostNotificationTrackerFactory() {}); } catch (SecurityException e) { if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { Loading