Android 12: Preventing System Update (and that nagging popup window)

This post was written by eli on August 24, 2022
Posted Under: Android,stop updates

Android is not my profession

I just want to say this first: I do completely different things for living. I’m not an Android expert, the last time I wrote code in Java was 20 years ago, and what’s written below is really not professional information nor advice. This is the stuff I found out while trying to get my phone under control.

When the phone doesn’t take “no” for an answer

This is an increasing phenomenon: Software treating us like kids, not asking what to do, not suggesting, but telling us what it’s about to do. Or just does it.

So let’s assume for a second that we’re grown-ups who can make our own decisions, and one of those decisions was not to update our phone’s software. Let’s leave the question if this is clever or not. It’s about who has control over the piece of electronics that you consider yours.

For the record, I would have been perfectly fine with having security patches applied frequently. The problem with updates is that there are “improvements” along with the security fixes, so there’s always something that suddenly doesn’t work after them. Because of a bug that will be fixed on the next update, of course.

The bad news is that to really stop the updates, you need a rooted phone, and if you haven’t done that yet, odds are that the price for doing that (i.e. wiping the phone completely) is much worse than an update itself.

Plus you need adb installed and know how to work with it (I discuss adb briefly in this post). Maybe that will change in the future, if there will be a simple app performing the necessary operations. Or maybe this will be integrated into the app that assists with rooting?

In case you want to get right to the point, jump to “Disabling activities” below. That’s where it says what to do.

All said below relates to Android 12, build SQ1D.220105.007 on a Google Pixel 6 Pro (and it quite appears like it’s going to stay that way).

That nagging popup window

After rooting the phone, there are two immediate actions that are supposed to turn off automatic system updates. Not that it helped much in my case, but here they are:

  • Under Developer Options (which are enabled for unlocking anyhow). Turn off “Automatic system updates”.
  • Go to Settings > Notifications > App Notifications > Google Play Settings and turn off System Update (to silence the “System update paused” notification)

And after some time, the famous “Install update to keep device secure” popup appears. And again, and again:

Screenshot of popup: "Install update to keep device secure"

Which is a nuisance, and there’s always the risk of mistakenly tap “Install now”. But then it escalated to “Install now to control when your device updates”:

Screenshot of popup: "Install now to control when your device updates"

This is a simple ultimatum, made by the machine I’m supposed to own: Either you do the update yourself, or I do it anyhow. And if that turns out to blow up your phone bill, your problem. How cute.

Who’s behind the popup window?

The first step is to figure out what package initiates the request for an update.

But even before that, it’s necessary to understand how Intents and Activities work together to put things on the screen.

This page explains Activities and their relations with Intents, and this page shows a simple code example on this matter.

Activities are (primarily?) foreground tasks that represent a piece of GUI interaction, that are always visible on the screen, and they get paused as soon as the user chooses something else on the GUI.

Then there’s a thing called Intent in Android, which is an abstraction for performing a certain operation. This allows any software component to ask for a certain task to be completed, and that translates into an Activity. So the idea is that any software components requests an operation as an Intent, and some other software component (or itself) listens for these requests, and carries out the matching Activity in response.

For example, WhatsApp (like many other apps) has an Intent published for sharing files (ACTION_SEND), so when any application wants to share something, it opens a menu of candidates for sharing (which is all Intents published for that purpose), and when the user selects WhatsApp to share with, the application calls the Activity that is registered by WhatsApp for that Intent. Which file to share is given as an “extra” to the Intent (which simply means that the call has an argument). Note that what actually happens is that WhatApp takes over the screen completely, which is exactly the idea behind Activities.

Now some hands-on. When the popup appears, go

$ adb shell dumpsys window windows > dump.txt

That produces a lot of output, but there was this segment:

  Window #10 Window{28d0746 u0 com.google.android.gms/com.google.android.gms.update.phone.PopupDialog}:
    mDisplayId=0 rootTaskId=26 mSession=Session{55a992b 9996:u0a10146} mClient=android.os.BinderProxy@95ec721
    mOwnerUid=10146 showForAllUsers=false package=com.google.android.gms appop=NONE
    mAttrs={(0,0)(wrapxwrap) sim={adjust=pan forwardNavigation} ty=BASE_APPLICATION fmt=TRANSLUCENT wanim=0x10302f4 surfaceInsets=Rect(112, 112 - 112, 112)
      fl=DIM_BEHIND SPLIT_TOUCH HARDWARE_ACCELERATED
      pfl=USE_BLAST INSET_PARENT_FRAME_BY_IME
      bhv=DEFAULT
      fitTypes=STATUS_BARS NAVIGATION_BARS CAPTION_BAR}
    Requested w=1120 h=1589 mLayoutSeq=4826
    mBaseLayer=21000 mSubLayer=0    mToken=ActivityRecord{577992f u0 com.google.android.gms/.update.phone.PopupDialog t26}
    mActivityRecord=ActivityRecord{577992f u0 com.google.android.gms/.update.phone.PopupDialog t26}
    mAppDied=false    drawnStateEvaluated=true    mightAffectAllDrawn=true
    mViewVisibility=0x0 mHaveFrame=true mObscured=false
    mGivenContentInsets=[0,0][0,0] mGivenVisibleInsets=[0,0][0,0]
    mFullConfiguration={1.0 425mcc1mnc [en_US,iw_IL,ar_PS] ldltr sw411dp w411dp h834dp 560dpi nrml long hdr widecg port night finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1440, 3120) mAppBounds=Rect(0, 130 - 1440, 3064) mMaxBounds=Rect(0, 0 - 1440, 3120) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} as.2 s.1 fontWeightAdjustment=0}
    mLastReportedConfiguration={1.0 425mcc1mnc [en_US,iw_IL,ar_PS] ldltr sw411dp w411dp h834dp 560dpi nrml long hdr widecg port night finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1440, 3120) mAppBounds=Rect(0, 130 - 1440, 3064) mMaxBounds=Rect(0, 0 - 1440, 3120) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} as.2 s.1 fontWeightAdjustment=0}
    mHasSurface=true isReadyForDisplay()=true mWindowRemovalAllowed=false
    Frames: containing=[0,145][1440,3064] parent=[0,145][1440,3064] display=[0,145][1440,3064]
    mFrame=[160,810][1280,2399] last=[160,810][1280,2399]
     surface=[112,112][112,112]
    WindowStateAnimator{9e9cbdf com.google.android.gms/com.google.android.gms.update.phone.PopupDialog}:
      mSurface=Surface(name=com.google.android.gms/com.google.android.gms.update.phone.PopupDialog)/@0x223f02c
      Surface: shown=true layer=0 alpha=1.0 rect=(0.0,0.0)  transform=(1.0, 0.0, 0.0, 1.0)
      mDrawState=HAS_DRAWN       mLastHidden=false
      mEnterAnimationPending=false      mSystemDecorRect=[0,0][0,0]
    mForceSeamlesslyRotate=false seamlesslyRotate: pending=null finishedFrameNumber=0
    mDrawLock=WakeLock{a82daf5 held=false, refCount=5}
    isOnScreen=true
    isVisible=true

Also, in the output of

$ adb shell dumpsys > all.txt

there was a much more to-the-point section saying (pretty much at the beginning of this huge file):

Display 4619827677550801152 HWC layers:
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 Layer name
           Z |  Window Type |  Comp Type |  Transform |   Disp Frame (LTRB) |          Source Crop (LTRB) |     Frame Rate (Explicit) (Seamlessness) [Focused]
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 Wallpaper BBQ wrapper#0
  rel      0 |         2013 |     DEVICE |          0 |    0    0 1440 3120 |   65.0  142.0 1375.0 2978.0 |                                              [ ]
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 com.google.android.apps.nexuslaunche[...]exuslauncher.NexusLauncherActivity#0
  rel      0 |            1 |     DEVICE |          0 |    0    0 1440 3120 |    0.0    0.0 1440.0 3120.0 |                                              [ ]
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 Dim Layer for - WindowedMagnification:0:31#0
  rel     -1 |            0 |     DEVICE |          0 |    0    0 1440 3120 |    0.0    0.0    0.0    0.0 |                                              [ ]
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 com.google.android.gms/com.google.android.gms.update.phone.PopupDialog#0
  rel      0 |            1 |     DEVICE |          0 |   48  698 1392 2511 |    0.0    0.0 1344.0 1813.0 |                                              [*]
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 StatusBar#0
  rel      0 |         2000 |     DEVICE |          0 |    0    0 1440  145 |    0.0    0.0 1440.0  145.0 |                                              [ ]
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 NavigationBar0#0
  rel      0 |         2019 |     DEVICE |          0 |    0 2952 1440 3120 |    0.0    0.0 1440.0  168.0 |                                              [ ]
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 ScreenDecorOverlay#0
  rel      0 |         2024 |     DEVICE |          0 |    0    0 1440  176 |    0.0    0.0 1440.0  176.0 |                                              [ ]
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 ScreenDecorOverlayBottom#0
  rel      0 |         2024 |     DEVICE |          0 |    0 2944 1440 3120 |    0.0    0.0 1440.0  176.0 |                                              [ ]
---------------------------------------------------------------------------------------------------------------------------------------------------------------

This is much better, because the window in focus is clearly marked. No need to guess.

Another place to look at is

$ adb shell dumpsys activity recents > recent.txt

Where it said:

  * Recent #0: Task{51b5cb8 #26 type=standard A=10146:com.google.android.gms U=0 visible=true mode=fullscreen translucent=true sz=1}
    userId=0 effectiveUid=u0a146 mCallingUid=u0a146 mUserSetupComplete=true mCallingPackage=com.google.android.gms mCallingFeatureId=com.google.android.gms.ota_base
    affinity=10146:com.google.android.gms
    intent={act=android.settings.SYSTEM_UPDATE_COMPLETE flg=0x10848000 pkg=com.google.android.gms cmp=com.google.android.gms/.update.phone.PopupDialog}
    mActivityComponent=com.google.android.gms/.update.phone.PopupDialog
    rootWasReset=false mNeverRelinquishIdentity=true mReuseTask=false mLockTaskAuth=LOCK_TASK_AUTH_PINNABLE
    Activities=[ActivityRecord{577992f u0 com.google.android.gms/.update.phone.PopupDialog t26}]
    askedCompatMode=false inRecents=true isAvailable=true
    taskId=26 rootTaskId=26
    hasChildPipActivity=false
    mHasBeenVisible=true
    mResizeMode=RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION mSupportsPictureInPicture=false isResizeable=true
    lastActiveTime=232790582 (inactive for 23s)

This is interesting, as it says which Intent and Activity stand behind the popup, just by asking what the last Activity requests were. Even more important, if the popup was dismissed or disappeared for any other reason, it can still be found here.

So no doubt, it’s com.google.android.gms that stands behind this popup. That’s Google Mobile Service, and it’s a package that is responsible for a whole lot. So disabling it is out of the question (and uninstalling impossible).

Under the section “ACTIVITY MANAGER PENDING INTENTS (dumpsys activity intents)” there was

    #8: PendingIntentRecord{50a35f1 com.google.android.gms/com.google.android.gms.ota_base broadcastIntent}
      uid=10146 packageName=com.google.android.gms featureId=com.google.android.gms.ota_base type=broadcastIntent flags=0x2000000
      requestIntent=act=com.google.android.chimera.IntentOperation.TARGETED_INTENT dat=chimeraio:/com.google.android.gms.chimera.GmsIntentOperationService/com.google.android.gms.update.INSTALL_UPDATE pkg=com.google.android.gms (has extras)
      sent=true canceled=false

which I suppose indicates that com.google.android.gms has requested to run its own .update.INSTALL_UPDATE at a later stage. In other words, this is the indication of the recurring request to run the INSTALL_UPDATE intent.

Disabling activities

The trick to disable the popup, as well as the update itself, is to disable certain Android Activities. This is inspired by this post in XDA forums.

First, find all activities in the system:

$ adb shell dumpsys package > dumpsys-package.txt

Then look for those specific to the package:

$ grep com.google.android.gms dumpsys-package.txt | less

Actually, one can narrow it down even more to those having the “.update.” substring:

$ grep com.google.android.gms dumpsys-package.txt | grep -i \\.update\\. | less

And eventually, disable what appears to be relevant activities (adb shell commands as root):

# pm disable com.google.android.gms/.update.SystemUpdateActivity
# pm disable com.google.android.gms/.update.SystemUpdateService
# pm disable com.google.android.gms/.update.SystemUpdateGcmTaskService

And possibly also the popup itself:

# pm disable com.google.android.gms/.update.phone.PopupDialog
# pm disable com.google.android.gms/.update.OtaSuggestionActivity

I’m not sure if all of these are necessary. The list might change across different versions of Android.

For each command, pm responds with a confirmation, e.g.

# pm disable com.google.android.gms/.update.SystemUpdateActivity
Component {com.google.android.gms/com.google.android.gms.update.SystemUpdateActivity} new state: disabled

And then reboot (not sure it’s necessary, but often “disable” doesn’t mean “stop”).

Are the changes in effect? Make a dump of the package:

$ adb shell pm dump com.google.android.gms > gms-dump.txt

and search for e.g. SystemUpdateActivity in the file. These features should appear under “disabledComponents:”.

However running “adb shell dumpsys activity intents”, it’s evident that the com.google.android.gms.update.INSTALL_UPDATE intent is still active. So this Intent will still be requested in the future. See below what happens with that.

So it’s quite clear that the popup can be disabled, but it’s less obvious what happens if the system wants to update itself when the relevant activity is disabled. Will it or will it not prevent the update? The proof is in the pudding.

So here’s the pudding

To begin with, the phone didn’t bug me again on updating, and neither has it done anything in that direction. Regardless (or not), there’s no problem updating all apps on the phone, and neither does it provoke any unwanted stuff. I’ve seen some speculations on the web, that System Update was somehow related to Google Play, and given my experience, I don’t think this is the case.

So disabling the Activities did the trick. It’s also possible to see exactly what happened by looking at the output of

$ adb shell logcat -d > all-log.txt

where it says this somewhere around the time it should have started messing around:

08-22 18:39:19.063  1461  1972 I ActivityTaskManager: START u0 {act=android.settings.SYSTEM_UPDATE_COMPLETE flg=0x10048000 pkg=com.google.android.gms (has extras)} from uid 10146
--------- beginning of crash
08-22 18:39:19.069  2838  7279 E AndroidRuntime: FATAL EXCEPTION: [com.google.android.gms.chimera.container.intentoperation.GmsIntentOperationChimeraService-Executor] idle
08-22 18:39:19.069  2838  7279 E AndroidRuntime: Process: com.google.android.gms, PID: 2838
08-22 18:39:19.069  2838  7279 E AndroidRuntime: android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.settings.SYSTEM_UPDATE_COMPLETE flg=0x10048000 pkg=com.google.android.gms (has extras) }
08-22 18:39:19.069  2838  7279 E AndroidRuntime: 	at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2087)
08-22 18:39:19.069  2838  7279 E AndroidRuntime: 	at android.app.Instrumentation.execStartActivity(Instrumentation.java:1747)
08-22 18:39:19.069  2838  7279 E AndroidRuntime: 	at android.app.ContextImpl.startActivity(ContextImpl.java:1085)
08-22 18:39:19.069  2838  7279 E AndroidRuntime: 	at android.app.ContextImpl.startActivity(ContextImpl.java:1056)
08-22 18:39:19.069  2838  7279 E AndroidRuntime: 	at android.content.ContextWrapper.startActivity(ContextWrapper.java:411)
08-22 18:39:19.069  2838  7279 E AndroidRuntime: 	at com.google.android.gms.update.reminder.UpdateReminderDialogIntentOperation.a(:com.google.android.gms@222615044@22.26.15 (190400-461192076):67)
08-22 18:39:19.069  2838  7279 E AndroidRuntime: 	at com.google.android.gms.update.reminder.UpdateReminderDialogIntentOperation.onHandleIntent(:com.google.android.gms@222615044@22.26.15 (190400-461192076):10)
08-22 18:39:19.069  2838  7279 E AndroidRuntime: 	at com.google.android.chimera.IntentOperation.onHandleIntent(:com.google.android.gms@222615044@22.26.15 (190400-461192076):2)
08-22 18:39:19.069  2838  7279 E AndroidRuntime: 	at uzr.onHandleIntent(:com.google.android.gms@222615044@22.26.15 (190400-461192076):4)
08-22 18:39:19.069  2838  7279 E AndroidRuntime: 	at ffi.run(:com.google.android.gms@222615044@22.26.15 (190400-461192076):3)
08-22 18:39:19.069  2838  7279 E AndroidRuntime: 	at ffh.run(:com.google.android.gms@222615044@22.26.15 (190400-461192076):11)
08-22 18:39:19.069  2838  7279 E AndroidRuntime: 	at cfvf.run(:com.google.android.gms@222615044@22.26.15 (190400-461192076):2)
08-22 18:39:19.069  2838  7279 E AndroidRuntime: 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
08-22 18:39:19.069  2838  7279 E AndroidRuntime: 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
08-22 18:39:19.069  2838  7279 E AndroidRuntime: 	at java.lang.Thread.run(Thread.java:1012)
08-22 18:39:19.092  1461  4210 I DropBoxManagerService: add tag=system_app_crash isTagEnabled=true flags=0x2
08-22 18:39:19.094  1461  1552 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
08-22 18:39:19.094  1461  1552 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
08-22 18:39:19.270  1461  4215 I ActivityManager: Process com.google.android.gms (pid 2838) has died: fg  SVC
08-22 18:39:19.271  1461  4215 W ActivityManager: Scheduling restart of crashed service com.google.android.gms/.chimera.GmsIntentOperationService in 1000ms for start-requested
08-22 18:39:20.273  1461  1552 D CompatibilityChangeReporter: Compat change id reported: 135634846; UID 10146; state: DISABLED
08-22 18:39:20.274  1461  1552 D CompatibilityChangeReporter: Compat change id reported: 177438394; UID 10146; state: DISABLED
08-22 18:39:20.274  1461  1552 D CompatibilityChangeReporter: Compat change id reported: 135772972; UID 10146; state: DISABLED
08-22 18:39:20.274  1461  1552 D CompatibilityChangeReporter: Compat change id reported: 135754954; UID 10146; state: ENABLED
08-22 18:39:20.275  1461  1553 D CompatibilityChangeReporter: Compat change id reported: 143937733; UID 10146; state: ENABLED
08-22 18:39:20.296  1461  1553 I ActivityManager: Start proc 7305:com.google.android.gms/u0a146 for service {com.google.android.gms/com.google.android.gms.chimera.GmsIntentOperationService}

Clearly, disabling the Activities made them ineligible for handling the SYSTEM_UPDATE_COMPLETE Intent, so an ActivityNotFoundException was thrown. Surprisingly enough, this exception wasn’t caught, so the com.google.android.gms process simply died and was quickly restarted by the system.

I also found these later on:

08-24 07:20:00.085 16126 25006 I SystemUpdate: [Execution,InstallationIntentOperation] Received intent: Intent { act=com.google.android.gms.update.INSTALL_UPDATE cat=[targeted_intent_op_prefix:.update.execution.InstallationIntentOperation] pkg=com.google.android.gms cmp=com.google.android.gms/.chimera.GmsIntentOperationService }.
08-24 07:20:00.114 16126 25006 W GmsTaskScheduler: com.google.android.gms.update.SystemUpdateGcmTaskService is not available. This may cause the task to be lost.
08-24 07:20:00.118 16126 25006 W GmsTaskScheduler: com.google.android.gms.update.SystemUpdateGcmTaskService is not available. This may cause the task to be lost.
08-24 07:20:00.119 16126 25006 W GmsTaskScheduler: com.google.android.gms.update.SystemUpdateGcmTaskService is not available. This may cause the task to be lost.

[ ... ]

08-24 07:20:00.198 16126 25006 I SystemUpdate: [Control,InstallationControl] Installation progress updated to (0x413, -1.000).
08-24 07:20:00.273 16126 25006 I SystemUpdate: [Control,ChimeraGcmTaskService] Scheduling task: DeviceIdle.
08-24 07:20:00.273 16126 25006 W GmsTaskScheduler: com.google.android.gms.update.SystemUpdateGcmTaskService is not available. This may cause the task to be lost.
08-24 07:20:00.274 16126 25006 I SystemUpdate: [Execution,ExecutionManager] Action streaming-apply executed for 0.17 seconds.
08-24 07:20:00.283 16126 25006 I SystemUpdate: [Execution,ExecutionManager] Action fixed-delay-execution executed for 0.01 seconds.

Bottom line: Disabling the activity causes the GMS service to die and restart every time it thinks about updating the system and/or nagging about it. So it won’t happen. Mission accomplished.

Actually, I’m not sure it’s possible to update the phone now, even if I wanted to.

It wasn’t obvious that this would work. Disabling the activity could have canceled just the GUI part of the update process. But it did.

It would have been more elegant to add another package, that supplies an Activity for the SYSTEM_UPDATE_COMPLETE Intent, that runs instead of the original, disabled one. This would have avoided this recurring crash. I don’t know if this is possible though.

Or even better, to disable the recurring call to this Intent. Ideas are welcome.

Or did it really work?

A couple of months later, and I noted something that looked like a huge download. Using “top” on “adb shell” revealed that the update_engine consumed some 60% CPU. Using logcat as above revealed several entries like these:

10-29 20:43:38.677 18745 24156 I SystemUpdate: [Control,InstallationControl] Installation progress updated to (0x111, 0.704).
10-29 20:43:38.717 18745 24156 I SystemUpdate: [Control,InstallationControl] Update engine status updated to 0x003.
10-29 20:43:38.832  1106  1106 I update_engine: [INFO:delta_performer.cc(115)] Completed 1691/2292 operations (73%), 1429588024/2027780609 bytes downloaded (70%), overall progress 71%

So there is definitely some download activity going on. The question is what will happen when it reaches 100%. And even more important, if there’s a way to prevent this from happening.

And maybe I’m just a bit too uptight. Using (as root)

# find /mnt/installer/0/emulated/ -cmin -1 2>/dev/null

to find newly update files, it appears like the activity is in /mnt/installer/0/emulated/0/Android/data/com.google.android.googlequicksearchbox/files/download_cache, and that the downloaded files are like soda-en-US-v7025-3.zip, which appears to be Speech On Device Access. And I’m fine with that.

Or did it really work II?

I owe this part to Namelesswonder’s very useful comment below. Indeed, the logs are at /data/misc/update_engine_log/, and there are indeed some files there from October, which is when there was some update activity. The progress of the download, in percents and bytes, is written in these logs.

The first log has an interesting row:

[1003/115142.712102] [INFO:main.cc(55)] A/B Update Engine starting
[1003/115142.720019] [INFO:boot_control_android.cc(68)] Loaded boot control hidl hal.
[1003/115142.738330] [INFO:update_attempter_android.cc(1070)] Scheduling CleanupPreviousUpdateAction.
[1003/115142.747361] [INFO:action_processor.cc(51)] ActionProcessor: starting CleanupPreviousUpdateAction

Any idea how to schedule CleanupPreviousUpdateAction manually? Sounds like fun to me.

The logs also name the URL of the downloaded package, https://ota.googlezip.net/packages/ota-api/package/d895ce906129e5138db6141ec735740cd1cd1b07.zip which is about 1.9 GB, so it’s definitely a full-blown update.

What is even more interesting from the logs is this line, which appears every time before starting or resuming an OTA download:

[1029/201649.018352] [INFO:delta_performer.cc(1009)] Verifying using certificates: /system/etc/security/otacerts.zip

It’s a ZIP file, which contains a single, bare-bone self-signed certificate in PEM format, which contains no fancy information.

It seems quite obvious that this certificate is used to authenticate the update file. What happens if this file is absent? No authentication, no update. Probably no download. So the immediate idea is to rename this file to something else. But it’s not that easy:

# mv otacerts.zip renamed-otacerts.zip
mv: bad 'otacerts.zip': Read-only file system

That’s true. /system/etc/security/ is on the root file system, and that happens to be a read-only one. But it’s a good candidate for a Magisk override, I guess.

Another thing that was mentioned in the said comment, is that the data goes to /data/ota_package/. That’s interesting, because in my system this directory is nearly empty, but the files’ modification timestamp is slightly before I neutralized the update activities, as mentioned above.

So it appears like the downloaded data goes directly into a raw partition, rather than a file.

There’s also /data/misc/update_engine/prefs/, which contains the current status of the download and other metadata. For example, update-state-next-data-offset apparently says how much has been downloaded already. What happens if this directory is nonexistent? Is it recreated, or is this too much for the updater to take?

As an initial approach, I renamed the “prefs” directory to “renamed-prefs”. A few days later, a new “prefs” directory was created, with just boot-id, previous-slot and previous-version files. Their timestamp matched a new log entry in update_engine_log/, which consisted of a CleanupPreviousUpdateAction. So apparently, the previous download was aborted and restarted.

So this time I renamed update_engine into something else, and added update_engine as a plain file. Before doing this, I tried to make the directory unreadable, but as root it’s accessible anyhow (in Android, not a normal Linux system). So the idea is to make it a non-directory, and maybe that will cause an irrecoverable failure.

Update 8.12.22: Nope, that didn’t help. A new log entry appeared in update_engine_log/, not a single word about the failure to create update_engine/prefs/, but a whole lot of rows indicating the download’s progress. /data/misc/update_engine remained as a plain file, so nothing could be stored in the correct place for the prefs/ subdirectory. I failed to find files with the names of those that are usually in prefs/ anywhere in the filesystem, so I wonder how the download will resume after it’s interrupted.


These are a few unrelated topics, which didn’t turn out helpful. So they’re left here, just in case.

Downloading the source

I had the idea of grabbing the sources for the GMS package, and see what went on there.

The list of Git repos is here (or the GitHub  mirror here). I went for the this one:

git clone https://android.googlesource.com/platform/frameworks/base

or the GitHub mirror:

git clone https://github.com/aosp-mirror/platform_frameworks_base.git

however this doesn’t seem to include the relevant parts.

Permissions

Another approach I had in mind was to turn off the permission to make a system update. I don’t know if there actually is such.

Android’s permission system allows granting and revoking permissions as required. Just typing “pm” (package manager) on adb shell returns help information

To get a list of all permissions in the system:

$ adb shell pm list permissions -g > list.txt

But that covers everything, so the trick is to search for the appearances of the specific package of interest. Something like

$ grep com.google.android.gms list.txt | sort | less

and look at that list. I didn’t anything that appears to related to system update.

Reader Comments

when I try:
adb shell pm disable com.google.android.gms/.update.SystemUpdateActivity

I get this response:
Exception occurred while executing ‘disable’:
java.lang.SecurityException: Shell cannot change component state for com.google.android.gms/com.google.android.gms.update.SystemUpdateActivity to 2

On Pixel 5 running Android 12

#1 
Written By joe A on November 5th, 2022 @ 16:17

It sounds like you’re not root. A regular user can’t disable activities.

#2 
Written By eli on November 5th, 2022 @ 16:20

update_engine was actually downloading an OTA update, as it only deals with downloading and applying OTA payloads to block devices.
The SODA models are just a red herring, they wouldn’t be the 2GB that update_engine is downloading, and you are also looking in the wrong place as the files wouldn’t be stored in the shared storage area.
The log files for update_engine are stored in /data/misc/update_engine_log/ with the payloads being stored in /data/ota_package/.

It’s bad news that update_engine is still being invoked somehow, and my memory is hazy as I read it something like 7 years ago but I believe Google Play Services will reenable it’s activities in an attempt to stop malware from interfering with it.

The best solution to prevent the forced payload downloads would be to make a Magisk module to overlay an empty file onto /system/bin/update_engine, rendering any attempt to download the OTA payloads impossible. Would also have the benefit of saving CPU cycles on update_engine being invoked periodically to calculate hashes on the partitions, which is going to fail on modified devices.

#3 
Written By Namelesswonder on November 10th, 2022 @ 05:50

Thanks a lot for this comment. I’ve added a clause in the post above in response.

#4 
Written By eli on November 10th, 2022 @ 08:53

Thank you so much, i try to find a way disable that notification with root module etc but no luck, until i found this site, i try and all work, god bless u bro

#5 
Written By aguz on September 23rd, 2023 @ 21:22

Add a Comment

required, use real name
required, will not be published
optional, your blog address