Loading packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +48 −18 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import android.content.om.FabricatedOverlay; import android.content.om.OverlayIdentifier; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Color; import android.net.Uri; Loading @@ -53,6 +54,7 @@ import android.util.SparseIntArray; import android.util.TypedValue; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.systemui.CoreStartable; Loading Loading @@ -114,10 +116,12 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { private final boolean mIsMonetEnabled; private final UserTracker mUserTracker; private final DeviceProvisionedController mDeviceProvisionedController; private final Resources mResources; // Current wallpaper colors associated to a user. private final SparseArray<WallpaperColors> mCurrentColors = new SparseArray<>(); private final WallpaperManager mWallpaperManager; private ColorScheme mColorScheme; @VisibleForTesting protected ColorScheme mColorScheme; // If fabricated overlays were already created for the current theme. private boolean mNeedsOverlayCreation; // Dominant color extracted from wallpaper, NOT the color used on the overlay Loading Loading @@ -344,7 +348,7 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { SecureSettings secureSettings, WallpaperManager wallpaperManager, UserManager userManager, DeviceProvisionedController deviceProvisionedController, UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags, WakefulnessLifecycle wakefulnessLifecycle) { @Main Resources resources, WakefulnessLifecycle wakefulnessLifecycle) { super(context); mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET); Loading @@ -358,6 +362,7 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { mSecureSettings = secureSettings; mWallpaperManager = wallpaperManager; mUserTracker = userTracker; mResources = resources; mWakefulnessLifecycle = wakefulnessLifecycle; dumpManager.registerDumpable(TAG, this); } Loading Loading @@ -466,8 +471,13 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { mMainWallpaperColor = mainColor; if (mIsMonetEnabled) { mThemeStyle = fetchThemeStyleFromSetting(); mSecondaryOverlay = getOverlay(mMainWallpaperColor, ACCENT, mThemeStyle); mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL, mThemeStyle); if (colorSchemeIsApplied()) { Log.d(TAG, "Skipping overlay creation. Theme was already: " + mColorScheme); return; } mNeedsOverlayCreation = true; if (DEBUG) { Log.d(TAG, "fetched overlays. accent: " + mSecondaryOverlay Loading @@ -493,7 +503,7 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { * Given a color candidate, return an overlay definition. */ protected @Nullable FabricatedOverlay getOverlay(int color, int type, Style style) { boolean nightMode = (mContext.getResources().getConfiguration().uiMode boolean nightMode = (mResources.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; mColorScheme = new ColorScheme(color, nightMode, style); Loading Loading @@ -525,6 +535,23 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { return overlay.build(); } /** * Checks if the color scheme in mColorScheme matches the current system palettes. */ private boolean colorSchemeIsApplied() { return mResources.getColor( android.R.color.system_accent1_500, mContext.getTheme()) == mColorScheme.getAccent1().get(6) && mResources.getColor(android.R.color.system_accent2_500, mContext.getTheme()) == mColorScheme.getAccent2().get(6) && mResources.getColor(android.R.color.system_accent3_500, mContext.getTheme()) == mColorScheme.getAccent3().get(6) && mResources.getColor(android.R.color.system_neutral1_500, mContext.getTheme()) == mColorScheme.getNeutral1().get(6) && mResources.getColor(android.R.color.system_neutral2_500, mContext.getTheme()) == mColorScheme.getNeutral2().get(6); } private void updateThemeOverlays() { final int currentUser = mUserTracker.getUserId(); final String overlayPackageJson = mSecureSettings.getStringForUser( Loading @@ -532,7 +559,6 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { currentUser); if (DEBUG) Log.d(TAG, "updateThemeOverlays. Setting: " + overlayPackageJson); final Map<String, OverlayIdentifier> categoryToPackage = new ArrayMap<>(); Style newStyle = mThemeStyle; if (!TextUtils.isEmpty(overlayPackageJson)) { try { JSONObject object = new JSONObject(overlayPackageJson); Loading @@ -543,25 +569,11 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { categoryToPackage.put(category, identifier); } } try { newStyle = Style.valueOf( object.getString(ThemeOverlayApplier.OVERLAY_CATEGORY_THEME_STYLE)); } catch (IllegalArgumentException e) { newStyle = Style.TONAL_SPOT; } } catch (JSONException e) { Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e); } } if (mIsMonetEnabled && newStyle != mThemeStyle) { mThemeStyle = newStyle; mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL, mThemeStyle); mSecondaryOverlay = getOverlay(mMainWallpaperColor, ACCENT, mThemeStyle); mNeedsOverlayCreation = true; } // Let's generate system overlay if the style picker decided to override it. OverlayIdentifier systemPalette = categoryToPackage.get(OVERLAY_CATEGORY_SYSTEM_PALETTE); if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) { Loading Loading @@ -626,6 +638,24 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { } } private Style fetchThemeStyleFromSetting() { Style style = mThemeStyle; final String overlayPackageJson = mSecureSettings.getStringForUser( Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, mUserTracker.getUserId()); if (!TextUtils.isEmpty(overlayPackageJson)) { try { JSONObject object = new JSONObject(overlayPackageJson); style = Style.valueOf( object.getString(ThemeOverlayApplier.OVERLAY_CATEGORY_THEME_STYLE)); } catch (JSONException | IllegalArgumentException e) { Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e); style = Style.TONAL_SPOT; } } return style; } @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { pw.println("mSystemColors=" + mCurrentColors); Loading packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +23 −6 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.content.BroadcastReceiver; import android.content.Intent; import android.content.om.FabricatedOverlay; import android.content.om.OverlayIdentifier; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Color; import android.os.Handler; Loading @@ -56,6 +57,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.monet.ColorScheme; import com.android.systemui.monet.Style; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.DeviceProvisionedController; Loading Loading @@ -108,6 +110,8 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { @Mock private FeatureFlags mFeatureFlags; @Mock private Resources mResources; @Mock private WakefulnessLifecycle mWakefulnessLifecycle; @Captor private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiver; Loading @@ -129,10 +133,20 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { when(mFeatureFlags.isEnabled(Flags.MONET)).thenReturn(true); when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE); when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true); mThemeOverlayController = new ThemeOverlayController(null /* context */, when(mResources.getColor(eq(android.R.color.system_accent1_500), any())) .thenReturn(Color.RED); when(mResources.getColor(eq(android.R.color.system_accent2_500), any())) .thenReturn(Color.GREEN); when(mResources.getColor(eq(android.R.color.system_accent3_500), any())) .thenReturn(Color.BLUE); when(mResources.getColor(eq(android.R.color.system_neutral1_500), any())) .thenReturn(Color.YELLOW); when(mResources.getColor(eq(android.R.color.system_neutral2_500), any())) .thenReturn(Color.BLACK); mThemeOverlayController = new ThemeOverlayController(mContext, mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier, mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController, mUserTracker, mDumpManager, mFeatureFlags, mWakefulnessLifecycle) { mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle) { @Nullable @Override protected FabricatedOverlay getOverlay(int color, int type, Style style) { Loading @@ -140,6 +154,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { when(overlay.getIdentifier()) .thenReturn(new OverlayIdentifier(Integer.toHexString(color | 0xff000000))); mCurrentStyle = style; mColorScheme = new ColorScheme(color, false /* nightMode */, style); return overlay; } }; Loading Loading @@ -643,16 +658,17 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { Executor executor = MoreExecutors.directExecutor(); mThemeOverlayController = new ThemeOverlayController(null /* context */, mThemeOverlayController = new ThemeOverlayController(mContext, mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier, mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController, mUserTracker, mDumpManager, mFeatureFlags, mWakefulnessLifecycle) { mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle) { @Nullable @Override protected FabricatedOverlay getOverlay(int color, int type, Style style) { FabricatedOverlay overlay = mock(FabricatedOverlay.class); when(overlay.getIdentifier()) .thenReturn(new OverlayIdentifier("com.thebest.livewallpaperapp.ever")); mColorScheme = new ColorScheme(color, false /* nightMode */, style); return overlay; } Loading @@ -679,16 +695,17 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { .thenReturn(new WallpaperColors(Color.valueOf(Color.GRAY), null, null)); Executor executor = MoreExecutors.directExecutor(); mThemeOverlayController = new ThemeOverlayController(null /* context */, mThemeOverlayController = new ThemeOverlayController(mContext, mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier, mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController, mUserTracker, mDumpManager, mFeatureFlags, mWakefulnessLifecycle) { mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle) { @Nullable @Override protected FabricatedOverlay getOverlay(int color, int type, Style style) { FabricatedOverlay overlay = mock(FabricatedOverlay.class); when(overlay.getIdentifier()) .thenReturn(new OverlayIdentifier(Integer.toHexString(color | 0xff000000))); mColorScheme = new ColorScheme(color, false /* nightMode */, style); return overlay; } }; Loading Loading
packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +48 −18 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import android.content.om.FabricatedOverlay; import android.content.om.OverlayIdentifier; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Color; import android.net.Uri; Loading @@ -53,6 +54,7 @@ import android.util.SparseIntArray; import android.util.TypedValue; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.systemui.CoreStartable; Loading Loading @@ -114,10 +116,12 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { private final boolean mIsMonetEnabled; private final UserTracker mUserTracker; private final DeviceProvisionedController mDeviceProvisionedController; private final Resources mResources; // Current wallpaper colors associated to a user. private final SparseArray<WallpaperColors> mCurrentColors = new SparseArray<>(); private final WallpaperManager mWallpaperManager; private ColorScheme mColorScheme; @VisibleForTesting protected ColorScheme mColorScheme; // If fabricated overlays were already created for the current theme. private boolean mNeedsOverlayCreation; // Dominant color extracted from wallpaper, NOT the color used on the overlay Loading Loading @@ -344,7 +348,7 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { SecureSettings secureSettings, WallpaperManager wallpaperManager, UserManager userManager, DeviceProvisionedController deviceProvisionedController, UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags, WakefulnessLifecycle wakefulnessLifecycle) { @Main Resources resources, WakefulnessLifecycle wakefulnessLifecycle) { super(context); mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET); Loading @@ -358,6 +362,7 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { mSecureSettings = secureSettings; mWallpaperManager = wallpaperManager; mUserTracker = userTracker; mResources = resources; mWakefulnessLifecycle = wakefulnessLifecycle; dumpManager.registerDumpable(TAG, this); } Loading Loading @@ -466,8 +471,13 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { mMainWallpaperColor = mainColor; if (mIsMonetEnabled) { mThemeStyle = fetchThemeStyleFromSetting(); mSecondaryOverlay = getOverlay(mMainWallpaperColor, ACCENT, mThemeStyle); mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL, mThemeStyle); if (colorSchemeIsApplied()) { Log.d(TAG, "Skipping overlay creation. Theme was already: " + mColorScheme); return; } mNeedsOverlayCreation = true; if (DEBUG) { Log.d(TAG, "fetched overlays. accent: " + mSecondaryOverlay Loading @@ -493,7 +503,7 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { * Given a color candidate, return an overlay definition. */ protected @Nullable FabricatedOverlay getOverlay(int color, int type, Style style) { boolean nightMode = (mContext.getResources().getConfiguration().uiMode boolean nightMode = (mResources.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; mColorScheme = new ColorScheme(color, nightMode, style); Loading Loading @@ -525,6 +535,23 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { return overlay.build(); } /** * Checks if the color scheme in mColorScheme matches the current system palettes. */ private boolean colorSchemeIsApplied() { return mResources.getColor( android.R.color.system_accent1_500, mContext.getTheme()) == mColorScheme.getAccent1().get(6) && mResources.getColor(android.R.color.system_accent2_500, mContext.getTheme()) == mColorScheme.getAccent2().get(6) && mResources.getColor(android.R.color.system_accent3_500, mContext.getTheme()) == mColorScheme.getAccent3().get(6) && mResources.getColor(android.R.color.system_neutral1_500, mContext.getTheme()) == mColorScheme.getNeutral1().get(6) && mResources.getColor(android.R.color.system_neutral2_500, mContext.getTheme()) == mColorScheme.getNeutral2().get(6); } private void updateThemeOverlays() { final int currentUser = mUserTracker.getUserId(); final String overlayPackageJson = mSecureSettings.getStringForUser( Loading @@ -532,7 +559,6 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { currentUser); if (DEBUG) Log.d(TAG, "updateThemeOverlays. Setting: " + overlayPackageJson); final Map<String, OverlayIdentifier> categoryToPackage = new ArrayMap<>(); Style newStyle = mThemeStyle; if (!TextUtils.isEmpty(overlayPackageJson)) { try { JSONObject object = new JSONObject(overlayPackageJson); Loading @@ -543,25 +569,11 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { categoryToPackage.put(category, identifier); } } try { newStyle = Style.valueOf( object.getString(ThemeOverlayApplier.OVERLAY_CATEGORY_THEME_STYLE)); } catch (IllegalArgumentException e) { newStyle = Style.TONAL_SPOT; } } catch (JSONException e) { Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e); } } if (mIsMonetEnabled && newStyle != mThemeStyle) { mThemeStyle = newStyle; mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL, mThemeStyle); mSecondaryOverlay = getOverlay(mMainWallpaperColor, ACCENT, mThemeStyle); mNeedsOverlayCreation = true; } // Let's generate system overlay if the style picker decided to override it. OverlayIdentifier systemPalette = categoryToPackage.get(OVERLAY_CATEGORY_SYSTEM_PALETTE); if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) { Loading Loading @@ -626,6 +638,24 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { } } private Style fetchThemeStyleFromSetting() { Style style = mThemeStyle; final String overlayPackageJson = mSecureSettings.getStringForUser( Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, mUserTracker.getUserId()); if (!TextUtils.isEmpty(overlayPackageJson)) { try { JSONObject object = new JSONObject(overlayPackageJson); style = Style.valueOf( object.getString(ThemeOverlayApplier.OVERLAY_CATEGORY_THEME_STYLE)); } catch (JSONException | IllegalArgumentException e) { Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e); style = Style.TONAL_SPOT; } } return style; } @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { pw.println("mSystemColors=" + mCurrentColors); Loading
packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +23 −6 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.content.BroadcastReceiver; import android.content.Intent; import android.content.om.FabricatedOverlay; import android.content.om.OverlayIdentifier; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Color; import android.os.Handler; Loading @@ -56,6 +57,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.monet.ColorScheme; import com.android.systemui.monet.Style; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.DeviceProvisionedController; Loading Loading @@ -108,6 +110,8 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { @Mock private FeatureFlags mFeatureFlags; @Mock private Resources mResources; @Mock private WakefulnessLifecycle mWakefulnessLifecycle; @Captor private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiver; Loading @@ -129,10 +133,20 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { when(mFeatureFlags.isEnabled(Flags.MONET)).thenReturn(true); when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE); when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true); mThemeOverlayController = new ThemeOverlayController(null /* context */, when(mResources.getColor(eq(android.R.color.system_accent1_500), any())) .thenReturn(Color.RED); when(mResources.getColor(eq(android.R.color.system_accent2_500), any())) .thenReturn(Color.GREEN); when(mResources.getColor(eq(android.R.color.system_accent3_500), any())) .thenReturn(Color.BLUE); when(mResources.getColor(eq(android.R.color.system_neutral1_500), any())) .thenReturn(Color.YELLOW); when(mResources.getColor(eq(android.R.color.system_neutral2_500), any())) .thenReturn(Color.BLACK); mThemeOverlayController = new ThemeOverlayController(mContext, mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier, mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController, mUserTracker, mDumpManager, mFeatureFlags, mWakefulnessLifecycle) { mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle) { @Nullable @Override protected FabricatedOverlay getOverlay(int color, int type, Style style) { Loading @@ -140,6 +154,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { when(overlay.getIdentifier()) .thenReturn(new OverlayIdentifier(Integer.toHexString(color | 0xff000000))); mCurrentStyle = style; mColorScheme = new ColorScheme(color, false /* nightMode */, style); return overlay; } }; Loading Loading @@ -643,16 +658,17 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { Executor executor = MoreExecutors.directExecutor(); mThemeOverlayController = new ThemeOverlayController(null /* context */, mThemeOverlayController = new ThemeOverlayController(mContext, mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier, mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController, mUserTracker, mDumpManager, mFeatureFlags, mWakefulnessLifecycle) { mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle) { @Nullable @Override protected FabricatedOverlay getOverlay(int color, int type, Style style) { FabricatedOverlay overlay = mock(FabricatedOverlay.class); when(overlay.getIdentifier()) .thenReturn(new OverlayIdentifier("com.thebest.livewallpaperapp.ever")); mColorScheme = new ColorScheme(color, false /* nightMode */, style); return overlay; } Loading @@ -679,16 +695,17 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { .thenReturn(new WallpaperColors(Color.valueOf(Color.GRAY), null, null)); Executor executor = MoreExecutors.directExecutor(); mThemeOverlayController = new ThemeOverlayController(null /* context */, mThemeOverlayController = new ThemeOverlayController(mContext, mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier, mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController, mUserTracker, mDumpManager, mFeatureFlags, mWakefulnessLifecycle) { mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle) { @Nullable @Override protected FabricatedOverlay getOverlay(int color, int type, Style style) { FabricatedOverlay overlay = mock(FabricatedOverlay.class); when(overlay.getIdentifier()) .thenReturn(new OverlayIdentifier(Integer.toHexString(color | 0xff000000))); mColorScheme = new ColorScheme(color, false /* nightMode */, style); return overlay; } }; Loading