Android 12: Preventing System Update (and that nagging popup window)
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:
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”:
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.
Update 8.5.24: For a long time, the phone hasn’t attempted to upgrade itself. Maybe because of the version gap. So in the end (?), I got some peace and quiet.
Maybe this is related?
/etc/init/ contains the services to be run during boot. One of them is called update_engine.rc, and another update_verifier.rc. Maybe delete these two files? I haven’t done this, because why mess with a phone that is OK now?
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
It sounds like you’re not root. A regular user can’t disable activities.
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.
Thanks a lot for this comment. I’ve added a clause in the post above in response.
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
Thank you so much! I just bought a new phone ( Moto G Play 2023 ) and I put a lot of work in having everything work the way I need. I do not need an upgrade to Android 13 and it is threatening me to do it!
I am a newbie; do you think that “Kingo Root” is a good rooting tool ? It should not unlock the bootloader, therefore I need not fear loss of data, right ?
Thanks again for you help
Guy L.
Laval, QC Canada
Hello,
I wasn’t aware of “Kingo Root”, to be honest, and I have no experience with it. But I’ve updated my other post on rooting to mention it.
It indeed claims to root the phone without unlocking it, so no data wiping. On the other hand, this tool breaks into your phone. I don’t know what to think about that.
Thank you very much for your prompt response! Your are such a nice person!
I will think twice about it, since I put a lot of hours on having the phone the way I want it…
If the upgrade breaks something, then I will redo everything from scratch. Re-initialize to factory, that should bring back Android 12, right ? And this time I shall unlock bootloader, root and all those good things…
Guy L.
Re-initialize to factory doesn’t downgrade the Android version, but rather deletes all user data and settings.
And frankly speaking, I don’t think an upgrade will change so much. I don’t like these upgrades, because I want my phone to behave in a stable manner. I might very well had upgraded my own phone to Android 13, but ironically enough, I won’t do that because the phone has stopped attempting to upgrade, and I suppose it will start over with the nagging after an upgrade to Android 13.
But even I don’t think the upgrade itself is such a big deal.