diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java index 61f06f1..4d2d95d 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java @@ -6,7 +6,9 @@ import android.content.res.Resources; import android.os.PowerManager; import android.view.View; import android.view.ViewGroup; +import android.widget.TextView; +import java.lang.ref.WeakReference; import java.util.ArrayDeque; import java.util.Collections; import java.util.IdentityHashMap; @@ -56,7 +58,7 @@ final class StockLayoutCanvasController { private final RuntimeContext runtimeContext; private final Map hiddenViewStates = new WeakHashMap<>(); - private final Map rootSizes = new WeakHashMap<>(); + private final Map rootGeometries = new WeakHashMap<>(); private final Map debugOverlaySignatures = new WeakHashMap<>(); private final Set confirmedAodPluginViews = Collections.newSetFromMap(new WeakHashMap<>()); @@ -75,6 +77,10 @@ final class StockLayoutCanvasController { private final DebugBoundsOverlayController debugBoundsOverlayController = new DebugBoundsOverlayController(); private final UnlockedIconSnapshotRenderController unlockedIconRenderController; + // The keyguard carrier text lives outside the statusbar root on this device. + // Keep a weak source reference so the lockscreen statusbar can render it when + // normal current-root anchor discovery cannot find it. + private WeakReference lockedCarrierTextSource = new WeakReference<>(null); private boolean hooksInstalled; @@ -188,7 +194,7 @@ final class StockLayoutCanvasController { return; } if (activeScene != null && (activeScene.isUnlocked() || activeScene.isLockscreen())) { - if (isKnownRootSizeChanged(root)) { + if (isKnownRootGeometryChanged(root)) { ownedIconHostManager.setUnlockedHostsVisible(root, false); removeDebugOverlay(root); } @@ -203,6 +209,7 @@ final class StockLayoutCanvasController { unlockedHosts.statusHost, unlockedHosts.notificationHost, activeScene); + ownedIconHostManager.bringUnlockedHostsToFront(root); } } } @@ -236,13 +243,18 @@ final class StockLayoutCanvasController { removeDebugOverlay(root); return; } + if (activeScene != null + && (activeScene.isUnlocked() || activeScene.isLockscreen()) + && resetUnlockedHostsForRootGeometryChange(root)) { + return; + } UnlockedHosts unlockedHosts = updateUnlockedOwnedHosts(root, activeScene); RenderResult renderResult = new RenderResult(false, false); if (unlockedHosts != null && (unlockedScene || activeScene != null && activeScene.isLockscreen())) { if (!unlockedScene) { restoreTrackedLockedStatusBarIconSurfaces(root); } - markRootSizeBeforeDraw(root); + markRootGeometryBeforeDraw(root); renderResult = unlockedIconRenderController.renderUnlocked( root, unlockedHosts.clockHost, @@ -250,13 +262,20 @@ final class StockLayoutCanvasController { unlockedHosts.chipHost, unlockedHosts.statusHost, unlockedHosts.notificationHost, - activeScene); - ownedIconHostManager.setUnlockedHostsVisible(root, true); + activeScene, + lockedCarrierTextSource()); + boolean shouldShowUnlockedHosts = unlockedScene || renderResult.shouldOwnLockscreen(); + ownedIconHostManager.setUnlockedHostsVisible(root, shouldShowUnlockedHosts); + if (shouldShowUnlockedHosts) { + ownedIconHostManager.bringUnlockedHostsToFront(root); + } } if (!unlockedScene) { trackedUnlockedStockSurfaces.clear(); restoreTransientUnlockedSources(); - hideOrDiscoverLockedStatusBarIconSurfaces(root); + if (renderResult.shouldOwnLockscreen()) { + hideOrDiscoverLockedStatusBarIconSurfaces(root); + } removeDebugOverlay(root); return; } @@ -421,6 +440,7 @@ final class StockLayoutCanvasController { private void trackAndHideLockedStatusBarIconSurfaces(View root) { walkTree(root, view -> { if (isLockedStatusBarIconSurface(view)) { + rememberLockedCarrierTextSource(view); trackedLockedStatusBarIconSurfaces.add(view); hideView(view, isCarrierView(view)); } @@ -509,13 +529,15 @@ final class StockLayoutCanvasController { } String idName = resolveIdName(view); String viewClassName = view.getClass().getName(); + boolean carrierSurface = idName.toLowerCase(java.util.Locale.ROOT).contains("carrier") + || viewClassName.toLowerCase(java.util.Locale.ROOT).contains("carrier"); boolean shelfSurface = viewClassName.contains("NotificationShelf"); boolean shadeIconOnlySurface = "notification_icononly_container".equals(idName) || "notification_icononly_area".equals(idName) || "keyguard_icononly_container_view".equals(idName) || "common_notification_widget".equals(idName); if (!("system_icons".equals(idName) - || idName.toLowerCase(java.util.Locale.ROOT).contains("carrier") + || carrierSurface || "system_icon_area".equals(idName) || "statusIcons".equals(idName) || "battery".equals(idName) @@ -537,6 +559,9 @@ final class StockLayoutCanvasController { || className.contains("StatusBarWindowView")) { return true; } + if (carrierSurface && className.contains("NotificationShadeWindowView")) { + return true; + } if (shelfSurface && className.contains("NotificationShadeWindowView")) { return true; } @@ -568,6 +593,32 @@ final class StockLayoutCanvasController { || className.contains("StatusBarWindowView"); } + private void rememberLockedCarrierTextSource(View root) { + if (lockedCarrierTextSource() != null || root == null || !isCarrierView(root)) { + return; + } + walkTree(root, view -> { + if (lockedCarrierTextSource() != null || !(view instanceof TextView textView)) { + return; + } + String idName = resolveIdName(textView); + String className = textView.getClass().getName(); + if ("keyguard_carrier_text".equals(idName) || className.contains("CarrierText")) { + lockedCarrierTextSource = new WeakReference<>(textView); + } + }); + } + + private TextView lockedCarrierTextSource() { + TextView source = lockedCarrierTextSource.get(); + if (source == null || !source.isAttachedToWindow()) { + lockedCarrierTextSource.clear(); + return null; + } + CharSequence text = source.getText(); + return text != null && text.length() > 0 ? source : null; + } + private boolean shouldHideNonChipView(View view) { if (view == null) { return false; @@ -637,32 +688,54 @@ final class StockLayoutCanvasController { || "ongoing_activity_capsule".equals(idName); } - private void markRootSizeBeforeDraw(View root) { + private void markRootGeometryBeforeDraw(View root) { if (!isUnlockedStatusBarRoot(root)) { return; } - markRootSize(root, new RootSize(root.getWidth(), root.getHeight())); + markRootGeometry(root, rootGeometry(root)); } - private boolean isKnownRootSizeChanged(View root) { + private boolean resetUnlockedHostsForRootGeometryChange(View root) { + if (!isKnownRootGeometryChanged(root)) { + return false; + } + markRootGeometryBeforeDraw(root); + unlockedIconRenderController.clearAll(); + ownedIconHostManager.removeUnlockedHosts(root); + unlockedHostsByRoot.remove(root); + removeDebugOverlay(root); + root.requestLayout(); + root.invalidate(); + return true; + } + + private boolean isKnownRootGeometryChanged(View root) { if (!isUnlockedStatusBarRoot(root)) { return false; } - RootSize previous = rootSizes.get(root); - return previous != null - && (previous.width != root.getWidth() || previous.height != root.getHeight()); + RootGeometry previous = rootGeometries.get(root); + RootGeometry current = rootGeometry(root); + return previous != null && !previous.equals(current); } - private void markRootSize(View root, RootSize size) { - if (size.width <= 0 || size.height <= 0) { + private void markRootGeometry(View root, RootGeometry geometry) { + if (geometry.width <= 0 || geometry.height <= 0) { return; } - RootSize previous = rootSizes.put(root, size); - if (previous != null && !previous.equals(size)) { + RootGeometry previous = rootGeometries.put(root, geometry); + if (previous != null && !previous.equals(geometry)) { unlockedIconRenderController.beginRootTransition(); } } + private RootGeometry rootGeometry(View root) { + int rotation = root != null && root.getDisplay() != null ? root.getDisplay().getRotation() : -1; + return new RootGeometry( + root != null ? root.getWidth() : 0, + root != null ? root.getHeight() : 0, + rotation); + } + private void enforceDirectAodControllerPolicy(Object controller) { if (controller == null) { return; @@ -758,7 +831,7 @@ final class StockLayoutCanvasController { if (!isUnlockedStatusBarRoot(root) || !layoutOffCleanedRoots.add(root)) { return; } - rootSizes.remove(root); + rootGeometries.remove(root); unlockedIconRenderController.clearAll(); ownedIconHostManager.removeUnlockedHosts(root); } @@ -805,7 +878,7 @@ final class StockLayoutCanvasController { if (!isUnlockedStatusBarRoot(root)) { return null; } - if (activeScene != null && activeScene.isUnlocked()) { + if (activeScene != null && (activeScene.isUnlocked() || activeScene.isLockscreen())) { UnlockedHosts cached = unlockedHostsByRoot.get(root); if (areUnlockedHostsAttached(root, cached)) { return cached; @@ -942,7 +1015,7 @@ final class StockLayoutCanvasController { ) { } - private record RootSize(int width, int height) { + private record RootGeometry(int width, int height, int rotation) { } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/OwnedIconHostManager.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/OwnedIconHostManager.java index a6ddbac..ea026ca 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/OwnedIconHostManager.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/OwnedIconHostManager.java @@ -95,6 +95,14 @@ public final class OwnedIconHostManager { setHostVisible(root, TAG_UNLOCKED_CHIP_HOST, visible); } + public void bringUnlockedHostsToFront(View root) { + bringHostToFront(root, TAG_UNLOCKED_STATUS_HOST); + bringHostToFront(root, TAG_UNLOCKED_NOTIFICATION_HOST); + bringHostToFront(root, TAG_UNLOCKED_CLOCK_HOST); + bringHostToFront(root, TAG_UNLOCKED_CARRIER_HOST); + bringHostToFront(root, TAG_UNLOCKED_CHIP_HOST); + } + public boolean isOwnedHost(View view) { if (view == null) { return false; @@ -128,6 +136,16 @@ public final class OwnedIconHostManager { host.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); } + private void bringHostToFront(View root, String tag) { + if (!(root instanceof ViewGroup parent) || tag == null) { + return; + } + FrameLayout host = findDirectHost(parent, tag); + if (host != null) { + host.bringToFront(); + } + } + private void disableAncestorClipping(View view) { Object current = view; int depth = 0; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotModels.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotModels.java index 42c85c6..8006fbb 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotModels.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotModels.java @@ -196,6 +196,18 @@ final class RootAnchors { && (clockContainer == null || isAttached(clockContainer)); } + RootAnchors withCarrierView(TextView carrierView) { + return new RootAnchors( + clockContainer, + clockView, + statusRoot, + statusEndContent, + statusIcons, + battery, + notificationRoot, + carrierView); + } + private static boolean isAttached(View view) { return view != null && view.isAttachedToWindow(); } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java index c9a9eb8..66a31b2 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java @@ -57,7 +57,8 @@ public final class UnlockedIconSnapshotRenderController { ViewGroup chipHost, ViewGroup statusHost, ViewGroup notificationHost, - SceneKey scene + SceneKey scene, + TextView externalCarrierView ) { if (scene == null || (!scene.isUnlocked() && !scene.isLockscreen()) || !root.isAttachedToWindow()) { clearAll(); @@ -70,6 +71,18 @@ public final class UnlockedIconSnapshotRenderController { layoutHostToRoot(root, statusHost); layoutHostToRoot(root, notificationHost); RootAnchors anchors = sourceCollector.anchorsFor(root); + if (scene.isLockscreen() + && settings.layoutCarrierEnabledLockscreen + && (anchors == null || anchors.carrierView == null)) { + anchors = sourceCollector.refreshAnchors(root); + } + if (scene.isLockscreen() + && settings.layoutCarrierEnabledLockscreen + && anchors != null + && anchors.carrierView == null + && externalCarrierView != null) { + anchors = anchors.withCarrierView(externalCarrierView); + } CollectedIcons previousIcons = lastCollectedIconsByRoot.get(root); LayoutInputSignature signature = sourceCollector.layoutInputSignature( root, @@ -84,7 +97,11 @@ public final class UnlockedIconSnapshotRenderController { boolean chipSourcesChanged = previousSignature == null || signature.chipSources != previousSignature.chipSources; if (signature.equals(previousSignature)) { - return new RenderResult(false, false); + return new RenderResult( + false, + false, + false, + !scene.isLockscreen() || lastLayoutPlanByRoot.get(root) != null); } lastLayoutSignatureByRoot.put(root, signature); Integer currentClockSourceGeometry = clockSourceGeometrySignature( @@ -156,6 +173,11 @@ public final class UnlockedIconSnapshotRenderController { settings, UnlockedLayoutPlanner.cutoutBounds(ViewGeometry.middleCutout(root), settings.clockMiddleCutoutGapPx)); } + if (scene.isLockscreen() && clockEnabledForScene(settings, scene) && currentClockLayout == null) { + clearLockscreenHosts(clockHost, carrierHost, chipHost, statusHost, notificationHost); + lastLayoutPlanByRoot.remove(root); + return new RenderResult(false, stockSourcesChanged, chipSourcesChanged, false); + } LayoutPlan layoutPlan = layoutPlanner.solve( root, clockHost, @@ -243,6 +265,20 @@ public final class UnlockedIconSnapshotRenderController { } } + private void clearLockscreenHosts( + ViewGroup clockHost, + ViewGroup carrierHost, + ViewGroup chipHost, + ViewGroup statusHost, + ViewGroup notificationHost + ) { + clockRenderer.clear(clockHost); + carrierRenderer.clear(carrierHost); + chipRenderer.clear(chipHost); + statusRenderer.clear(statusHost); + notificationRenderer.clear(notificationHost); + } + private void layoutHostToRoot(View root, ViewGroup host) { if (root == null || host == null || root.getWidth() <= 0 || root.getHeight() <= 0) { return; @@ -817,10 +853,15 @@ public final class UnlockedIconSnapshotRenderController { public record RenderResult( boolean layoutChanged, boolean stockSourcesChanged, - boolean chipSourcesChanged + boolean chipSourcesChanged, + boolean shouldOwnLockscreen ) { public RenderResult(boolean layoutChanged, boolean stockSourcesChanged) { - this(layoutChanged, stockSourcesChanged, stockSourcesChanged); + this(layoutChanged, stockSourcesChanged, stockSourcesChanged, true); + } + + public RenderResult(boolean layoutChanged, boolean stockSourcesChanged, boolean chipSourcesChanged) { + this(layoutChanged, stockSourcesChanged, chipSourcesChanged, true); } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java index 2a69b53..986e2e4 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java @@ -168,6 +168,7 @@ final class UnlockedLayoutPlanner { } ArrayList itemOrder = orderedKinds(settings.layoutItemOrder); splitClockIfNeeded(root, bands, itemOrder, clockCutoutBounds, settings); + sortBandsForItemOrder(bands, itemOrder); LayoutEdges edges = layoutEdges(root, settings, anchors); int itemPadding = Math.max(0, settings.layoutItemPaddingPx); solveWithPackedLayout(root, edges, bands, layoutCutoutBounds, settings, itemPadding); @@ -198,6 +199,30 @@ final class UnlockedLayoutPlanner { carrierPlacement); } + private void sortBandsForItemOrder( + ArrayList bands, + ArrayList itemOrder + ) { + if (bands == null || bands.size() < 2 || itemOrder == null || itemOrder.isEmpty()) { + return; + } + ArrayList originalOrder = new ArrayList<>(bands); + bands.sort((left, right) -> { + int orderComparison = Integer.compare( + orderIndex(itemOrder, left != null ? left.kind : null), + orderIndex(itemOrder, right != null ? right.kind : null)); + if (orderComparison != 0) { + return orderComparison; + } + return Integer.compare(originalOrder.indexOf(left), originalOrder.indexOf(right)); + }); + } + + private int orderIndex(ArrayList itemOrder, LayoutItemKind kind) { + int index = itemOrder != null ? itemOrder.indexOf(kind) : -1; + return index >= 0 ? index : Integer.MAX_VALUE; + } + private void solveWithPackedLayout( View root, LayoutEdges edges, @@ -495,19 +520,25 @@ final class UnlockedLayoutPlanner { return null; } Bounds bounds = ViewGeometry.boundsRelativeTo(root, collectedIcons.carrierView); - if (bounds == null || bounds.width <= 0 || bounds.height <= 0) { - return null; + Bounds anchorBounds = bounds != null + ? bounds + : ViewGeometry.boundsRelativeTo(root, collectedIcons.clockView); + if (anchorBounds == null || anchorBounds.height <= 0) { + anchorBounds = new Bounds(0, 0, Math.max(0, root.getWidth()), Math.max(1, root.getHeight())); } + int sourceHeight = collectedIcons.carrierView.getHeight() > 0 + ? collectedIcons.carrierView.getHeight() + : collectedIcons.carrierView.getMeasuredHeight(); int baseline = collectedIcons.carrierView.getBaseline() > 0 ? collectedIcons.carrierView.getBaseline() - : Math.max(0, (bounds.height * 3) / 4); + : Math.max(0, ((sourceHeight > 0 ? sourceHeight : anchorBounds.height) * 3) / 4); int verticalOffset = offsetScaler.clock( settings.layoutCarrierVerticalOffsetPx, collectedIcons.carrierView); return ClockLayout.simpleText( collectedIcons.carrierView, positionFor(settings.layoutCarrierPosition), - bounds.top + baseline - verticalOffset); + anchorBounds.top + baseline - verticalOffset); } private int statusCountForScene(SbtSettings settings, SceneKey scene) { @@ -707,6 +738,7 @@ final class UnlockedLayoutPlanner { ) { int left = firstUsableInset( rootWidth, + leftInset(root, anchors != null ? anchors.clockView : null), leftInset(root, anchors != null ? anchors.clockContainer : null), leftInset(root, anchors != null ? anchors.notificationRoot : null), root != null ? root.getPaddingLeft() : 0); diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedStockSourceCollector.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedStockSourceCollector.java index 81d0ec5..11c29ad 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedStockSourceCollector.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedStockSourceCollector.java @@ -28,6 +28,10 @@ final class UnlockedStockSourceCollector { if (cached != null && cached.isUsable()) { return cached; } + return refreshAnchors(root); + } + + RootAnchors refreshAnchors(View root) { RootAnchors anchors = discoverAnchors(root); anchorsByRoot.put(root, anchors); return anchors; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettings.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettings.java index f47f9c0..6a7878e 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettings.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettings.java @@ -3,7 +3,6 @@ package se.ajpanton.statusbartweak.shell.settings; import android.content.Context; import android.content.SharedPreferences; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import java.util.ArrayList; import java.util.Collections; @@ -862,60 +861,6 @@ public final class SbtSettings { } } - public static void restoreCredentialProtectedPreferences(Context context) { - if (context == null || Build.VERSION.SDK_INT < 24) { - return; - } - try { - Context deviceContext = context.createDeviceProtectedStorageContext(); - if (deviceContext == null) { - return; - } - SharedPreferences devicePrefs = deviceContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - Map deviceValues = devicePrefs.getAll(); - if (deviceValues == null || deviceValues.isEmpty()) { - return; - } - SharedPreferences normalPrefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - Map normalValues = normalPrefs.getAll(); - SharedPreferences.Editor editor = normalPrefs.edit(); - boolean changed = false; - for (Map.Entry entry : deviceValues.entrySet()) { - String key = entry.getKey(); - if (key == null || normalValues.containsKey(key)) { - continue; - } - Object value = entry.getValue(); - if (value instanceof Boolean bool) { - editor.putBoolean(key, bool); - } else if (value instanceof Integer integer) { - editor.putInt(key, integer); - } else if (value instanceof Long longValue) { - editor.putLong(key, longValue); - } else if (value instanceof Float floatValue) { - editor.putFloat(key, floatValue); - } else if (value instanceof String string) { - editor.putString(key, string); - } else if (value instanceof Set set) { - HashSet strings = new HashSet<>(); - for (Object item : set) { - if (item instanceof String stringItem) { - strings.add(stringItem); - } - } - editor.putStringSet(key, strings); - } else { - continue; - } - changed = true; - } - if (changed) { - editor.commit(); - } - } catch (Throwable ignored) { - } - } - private static boolean hasSettingsProvider(Context context) { if (context == null) { return false; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettingsProvider.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettingsProvider.java index 02f093a..318feb9 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettingsProvider.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettingsProvider.java @@ -35,7 +35,6 @@ public class SbtSettingsProvider extends ContentProvider { if (ctx == null) { return null; } - SbtSettings.restoreCredentialProtectedPreferences(ctx); SharedPreferences prefs = ctx.getSharedPreferences(SbtSettings.PREFS_NAME, Context.MODE_PRIVATE); if (METHOD_GET_UNLOCKED.equals(method)) { int value = prefs.getInt(SbtSettings.KEY_UNLOCKED_MAX_ICONS_PER_ROW, diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/MainActivity.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/MainActivity.java index 2f6b64a..41f5c3d 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/MainActivity.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/MainActivity.java @@ -9,21 +9,23 @@ import android.view.MenuItem; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; import androidx.core.view.GravityCompat; -import androidx.drawerlayout.widget.DrawerLayout; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; import androidx.fragment.app.Fragment; import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.navigation.NavigationView; import se.ajpanton.statusbartweak.R; -import se.ajpanton.statusbartweak.shell.settings.SbtSettings; public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { private static final String EXTRA_OPEN_NAV_ITEM = "open_nav_item"; - private DrawerLayout drawerLayout; + private SafeInsetDrawerLayout drawerLayout; private NavigationView navigationView; + private Integer navigationBasePaddingLeft; public static Intent newOpenDebugIntent(Context context) { Intent intent = new Intent(context, MainActivity.class); @@ -35,7 +37,6 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - SbtSettings.restoreCredentialProtectedPreferences(this); setContentView(R.layout.activity_main); MaterialToolbar toolbar = findViewById(R.id.toolbar); @@ -45,6 +46,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On navigationView = findViewById(R.id.nav_view); navigationView.setNavigationItemSelectedListener(this); navigationView.getMenu().setGroupCheckable(0, true, true); + applyNavigationDrawerInsets(); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( this, @@ -182,4 +184,41 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On intent.removeExtra(EXTRA_OPEN_NAV_ITEM); } } + + private void applyNavigationDrawerInsets() { + drawerLayout.addOnLayoutChangeListener((view, left, top, right, bottom, + oldLeft, oldTop, oldRight, oldBottom) -> + applyNavigationDrawerMarginsFromRootInsets()); + drawerLayout.post(this::applyNavigationDrawerMarginsFromRootInsets); + } + + private void applyNavigationDrawerMarginsFromRootInsets() { + WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(drawerLayout); + if (insets == null) { + return; + } + Insets safeInsets = insets.getInsetsIgnoringVisibility( + WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout()); + applyNavigationDrawerSafeArea(safeInsets.left, safeInsets.right); + } + + private void applyNavigationDrawerSafeArea(int leftInset, int rightInset) { + int safeLeft = Math.max(Math.max(0, leftInset), Math.max(0, drawerLayout.getPaddingLeft())); + int safeRight = Math.max(Math.max(0, rightInset), Math.max(0, drawerLayout.getPaddingRight())); + drawerLayout.setDrawerSafeInsets(safeLeft, safeRight); + if (navigationBasePaddingLeft == null) { + navigationBasePaddingLeft = navigationView.getPaddingLeft(); + } + int leftPadding = navigationBasePaddingLeft + safeLeft; + if (navigationView.getPaddingLeft() != leftPadding + || Math.round(navigationView.getTranslationX()) != 0) { + navigationView.setTranslationX(0f); + navigationView.setPadding( + leftPadding, + navigationView.getPaddingTop(), + navigationView.getPaddingRight(), + navigationView.getPaddingBottom()); + navigationView.requestLayout(); + } + } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/SafeInsetDrawerLayout.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/SafeInsetDrawerLayout.java new file mode 100644 index 0000000..40f9487 --- /dev/null +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/SafeInsetDrawerLayout.java @@ -0,0 +1,51 @@ +package se.ajpanton.statusbartweak.shell.ui; + +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; + +import androidx.drawerlayout.widget.DrawerLayout; + +public class SafeInsetDrawerLayout extends DrawerLayout { + private int safeInsetLeft; + private int safeInsetRight; + + public SafeInsetDrawerLayout(Context context) { + super(context); + } + + public SafeInsetDrawerLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SafeInsetDrawerLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setDrawerSafeInsets(int left, int right) { + int safeLeft = Math.max(0, left); + int safeRight = Math.max(0, right); + if (safeInsetLeft == safeLeft && safeInsetRight == safeRight) { + return; + } + safeInsetLeft = safeLeft; + safeInsetRight = safeRight; + invalidate(); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + if (safeInsetLeft == 0 && safeInsetRight == 0) { + super.dispatchDraw(canvas); + return; + } + int save = canvas.save(); + canvas.clipRect( + safeInsetLeft, + 0, + Math.max(safeInsetLeft, getWidth() - safeInsetRight), + getHeight()); + super.dispatchDraw(canvas); + canvas.restoreToCount(save); + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 6992358..5c0d0a3 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,5 +1,5 @@ - - +