Skip to content

feat: add home screen layout backup and restore - #455

Closed
drzraf wants to merge 1 commit into
FossifyOrg:mainfrom
drzraf:feature/home-screen-layout-backup
Closed

drzraf wants to merge 1 commit into
FossifyOrg:mainfrom
drzraf:feature/home-screen-layout-backup

Conversation

@drzraf

@drzraf drzraf commented Sep 30, 2026 •

Copy link
Copy Markdown

Adds a "Backup & restore" section to Settings that exports the home screen icons and folders to a JSON file and imports them back, through the Storage Access Framework.

Type of change(s)

  • Bug fix
  • Feature / enhancement
  • Infrastructure / tooling (CI, build, deps, tests)
  • Documentation

What changed and why

  • Provisioning the screen is a time-consuming and uninteresting activity which is why Layout backup (import/export) #94 collected so much traction.
  • Adds a "Backup & restore" section to Settings that exports the home screen layout to a JSON file and imports it back, via the Storage Access Framework.
  • Instead of "hidden overlapping" (two identical apps for a slot) or forcefully emptying existing slots and risking hidden I thought that for the price of such a check we could provide for two distinct provisioning scenario: Regular provisioning (empty everything then import), conditional composition (existing are preserved and have priority for a given) which is particularly handy since it allows to have distinct JSON file to import specific screens (thus the ability to have functionally distinct/granular backup) and the "restored items have priority in case of conflict" which is just a logical complement that comes for free once the previous are implemented.
  • Added a LayoutBackupHelper and its importLayout / exportLayout methods and the 3 ImportMode constants.

Tests performed

Both exports and imports (including app' with different page counts/configurations)

Before & after preview

New menu:

ignoreImageMinify

Import logic choosing:

ignoreImageMinify

Closes the following issue(s)

Checklist

  • I read the contribution guidelines.
  • I manually tested my changes on device and **emulator.
  • I updated the "Unreleased" section in CHANGELOG.md.
  • I have self-reviewed my pull request (no typos, formatting errors, etc.).
  • I understand every change in this pull request.
  • Opus 5 involvement

Detailed description

A backup keeps each item's page, its cell on that page, its dock flag, and for an icon in a folder its slot in that
folder, so multiple home screens, the dock and folder contents all survive the round trip.

Widgets and pinned shortcuts are deliberately left out in both directions: a widget id only means something to the
AppWidgetHost that allocated it, and a shortcut id only to the app that pinned it, so restoring either on another device
would bind to an unrelated widget or to nothing at all.

Icons and folders are just grid coordinates plus a package/activity reference, so they can be checked against this
device before anything is written.

Import asks what to do with cells that are already in use:

  • keep the current icons and give restored ones a free cell
  • let restored icons take their cell and move the current occupant
  • drop the current icons and folders first.

In all three, no two items end up on the same cell, which also covers items of the file colliding with each other.
A free cell is looked for from the item's own page onwards and never on an earlier one, so a full page spills onto the
next and the page grouping of the file is kept.

The dock is one row shared by every page, so a docked item only competes for dock cells, and a widget is never pushed
aside because it spans several cells. An item is skipped when its app is not installed, when it does not fit this
device's grid, or when no free cell is left for it. Matching against the installed apps falls back to the package name
when the stored activity name is empty, because the launcher writes its own default icons that way (see
MainActivity.getDefaultAppPackages) and launches them through getLaunchIntentForPackage; requiring the full
package/activity identifier would have skipped every one of those.

Export reads the database on a background thread, since Room is built without allowMainThreadQueries.
Reading a picked file reports provider and parse failures separately, and neither path lets an exception reach the
background thread's uncaught handler: a document provider lives in another process and is free to marshal back arbitrary
unchecked exceptions, which would otherwise take down the launcher instead of showing an error.

Export file sample

{
  "version": 1,
  "items": [
    {
      "tempId": 3,
      "parentTempId": null,
      "left": 3,
      "top": 5,
      "right": 3,
      "bottom": 5,
      "page": 0,
      "packageName": "org.mozilla.fennec_fdroid",
      "activityName": "",
      "title": "Fennec",
      "type": 0,
      "className": "",
      "docked": true
    },
    {
      "tempId": 6,
      "parentTempId": null,
      "left": 4,
      "top": 5,
      "right": 4,
      "bottom": 5,
      "page": 0,
      "packageName": "com.android.settings",
      "activityName": "com.android.settings.Settings",
      "title": "Paramètres",
      "type": 0,
      "className": "",
      "docked": true
    },
    {
      "tempId": 7,
      "parentTempId": null,
      "left": 1,
      "top": 5,
      "right": 1,
      "bottom": 5,
      "page": 0,
      "packageName": "org.fossify.phone",
      "activityName": "org.fossify.phone.activities.SplashActivity.Green",
      "title": "Téléphone",
      "type": 0,
      "className": "",
      "docked": true
    }
  ]
}

Adds a "Backup & restore" section to Settings that exports the home
screen icons and folders to a JSON file and imports them back, through
the Storage Access Framework. A backup keeps each item's page, its cell
on that page, its dock flag, and for an icon in a folder its slot in
that folder, so multiple home screens, the dock and folder contents all
survive the round trip.

Widgets and pinned shortcuts are deliberately left out in both
directions: a widget id only means something to the AppWidgetHost that
allocated it, and a shortcut id only to the app that pinned it, so
restoring either on another device would bind to an unrelated widget or
to nothing at all. Icons and folders are just grid coordinates plus a
package/activity reference, so they can be checked against this device
before anything is written.

Import asks what to do with cells that are already in use: keep the
current icons and give restored ones a free cell, let restored icons
take their cell and move the current occupant, or drop the current icons
and folders first. In all three, no two items end up on the same cell,
which also covers items of the file colliding with each other. A free
cell is looked for from the item's own page onwards and never on an
earlier one, so a full page spills onto the next and the page grouping
of the file is kept. The dock is one row shared by every page, so a
docked item only competes for dock cells, and a widget is never pushed
aside because it spans several cells.

An item is skipped when its app is not installed, when it does not fit
this device's grid, or when no free cell is left for it. Matching
against the installed apps falls back to the package name when the
stored activity name is empty, because the launcher writes its own
default icons that way (see MainActivity.getDefaultAppPackages) and
launches them through getLaunchIntentForPackage; requiring the full
package/activity identifier would have skipped every one of those.

Export reads the database on a background thread, since Room is built
without allowMainThreadQueries. Reading a picked file reports provider
and parse failures separately, and neither path lets an exception reach
the background thread's uncaught handler: a document provider lives in
another process and is free to marshal back arbitrary unchecked
exceptions, which would otherwise take down the launcher instead of
showing an error.

Closes FossifyOrg#94
@drzraf
drzraf requested a review from naveensingh as a code owner September 30, 2026 02:11
@fossifybot

fossifybot Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Fossify accepts code contributions only for open issues labeled help wanted. This pull request does not meet that requirement or one of the documented exceptions, so it is being closed without review. Please read the contribution guidelines before starting work.

@fossifybot fossifybot Bot closed this Sep 30, 2026
@drzraf drzraf mentioned this pull request Sep 30, 2026
6 of 7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Layout backup (import/export)

1 participant