Wie SystemUI in Android funktioniert



In diesem Artikel werde ich die Architektur und das Funktionsprinzip der Hauptanwendung von Android - SystemUI - analysieren. Ich habe mich für dieses Thema interessiert, weil ich mich frage, wie das System funktioniert, das von einer so großen Anzahl von Benutzern verwendet wird und für das täglich Tausende von Anwendungen auf Google Play oder einfach im Internet heruntergeladen werden. Außerdem interessiert mich das Problem der Informationssicherheit von Android und die dafür erstellten Anwendungen.

Unter Android ist SystemUI eine Anwendung, deren Quellcodepfad sich in platform_frameworks_base / packages / SystemUI / befindet , auf dem Gerät in system / priv-app / -SystemUI.

Die priv-App ist das Verzeichnis, in dem privilegierte Anwendungen gespeichert sind. Auf dem System- / App-Pfad befinden sich übrigens vorinstallierte Anwendungen, und normale Anwendungen, die wir selbst auf unserem Gerät installieren, werden in Daten / Apps gespeichert.

Dies wirft sofort die Frage auf: Warum können nicht alle vorinstallierten und privilegierten Anwendungen in ein Verzeichnis verschoben werden, warum ist diese Trennung erforderlich?

Tatsache ist, dass einige Anwendungen systematischer sind als andere :) Und diese Trennung ist erforderlich, um die Exploit-Abdeckung von Systemanwendungen zu verringern und Zugriff auf geschützte Vorgänge zu erhalten. Sie können eine Anwendung erstellen, die über eine spezielle ApplicationInfo.FLAG_SYSTEM verfügt und mehr Rechte im System erhält. Eine apk-Datei mit dieser Berechtigung wird jedoch im Systemabschnitt abgelegt.

SystemUI ist also eine APK-Datei, die im Wesentlichen eine normale Anwendung ist. Wenn Sie sich jedoch das komplexe SystemUI-Gerät ansehen, scheint es nicht mehr nur eine einfache Anwendung zu sein, oder?

Diese Anwendung führt sehr wichtige Funktionen aus:


  • Navigation
  • Aktuelle Anwendungen
  • Schnelleinstellungen
  • Benachrichtigungsleiste
  • Bildschirm sperren
  • Lautstärkeregler
  • Startbildschirm
  • ...

Starten Sie SystemUI


Wie ich oben sagte, ist SystemUI nicht wie eine reguläre Anwendung, so dass der Start nicht mit dem Start von Aktivitäten einhergeht, wie dies bei den meisten Anwendungen der Fall ist. SystemUI ist eine globale Benutzeroberfläche , die während des Systemstartvorgangs gestartet wird und nicht abgeschlossen werden kann.

<application android:name=".SystemUIApplication" android:persistent="true" android:allowClearUserData="false" android:allowBackup="false" android:hardwareAccelerated="true" android:label="@string/app_label" android:icon="@drawable/icon" android:process="com.android.systemui" android:supportsRtl="true" android:theme="@style/Theme.SystemUI" android:defaultToDeviceProtectedStorage="true" android:directBootAware="true" android:appComponentFactory="androidx.core.app.CoreComponentFactory"> 

Wenn wir uns mit SystemServer befassen, einer der beiden Säulen in der Android-Welt (die zweite ist Zygote, aber ich werde ein anderes Mal darüber sprechen), können wir den Ort finden, an dem SystemUI startet, wenn das System startet .

  static final void startSystemUi(Context context, WindowManagerService windowManager) { Intent intent = new Intent(); intent.setComponent(new ComponentName("com.android.systemui", "com.android.systemui.SystemUIService")); intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING); //Slog.d(TAG, "Starting service: " + intent); context.startServiceAsUser(intent, UserHandle.SYSTEM); windowManager.onSystemUiStarted(); } 

Hier sehen wir, wie der SystemUI-Dienst die nicht öffentliche API startServiceAsUser verwendet. Wenn Sie dies nutzen möchten, müssen Sie auf Reflexion zurückgreifen. Wenn Sie sich jedoch für die Verwendung der Reflection-API in Android entscheiden, überlegen Sie ein paar Mal, ob es sich lohnt. Denk hundertmal nach :)

Hier erstellen wir also einen separaten Prozess für die Anwendung. Tatsächlich ist jeder Abschnitt der SystemUI ein separater Dienst oder ein unabhängiges Modul.

 public abstract class SystemUI implements SysUiServiceProvider { public Context mContext; public Map<Class<?>, Object> mComponents; public abstract void start(); protected void onConfigurationChanged(Configuration newConfig) { } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { } protected void onBootCompleted() { } @SuppressWarnings("unchecked") public <T> T getComponent(Class<T> interfaceType) { return (T) (mComponents != null ? mComponents.get(interfaceType) : null); } public <T, C extends T> void putComponent(Class<T> interfaceType, C component) { if (mComponents != null) { mComponents.put(interfaceType, component); } } public static void overrideNotificationAppName(Context context, Notification.Builder n, boolean system) { final Bundle extras = new Bundle(); String appName = system ? context.getString(com.android.internal.R.string.notification_app_name_system) : context.getString(com.android.internal.R.string.notification_app_name_settings); extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, appName); n.addExtras(extras); } } 

Die Methode start () wird aufgerufen, um jeden Dienst zu starten, die unten aufgeführt sind.

 <string-array name="config_systemUIServiceComponents" translatable="false"> <item>com.android.systemui.Dependency</item> <item>com.android.systemui.util.NotificationChannels</item> <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item> <item>com.android.systemui.keyguard.KeyguardViewMediator</item> <item>com.android.systemui.recents.Recents</item> <item>com.android.systemui.volume.VolumeUI</item> <item>com.android.systemui.stackdivider.Divider</item> <item>com.android.systemui.SystemBars</item> <item>com.android.systemui.usb.StorageNotification</item> <item>com.android.systemui.power.PowerUI</item> <item>com.android.systemui.media.RingtonePlayer</item> <item>com.android.systemui.keyboard.KeyboardUI</item> <item>com.android.systemui.pip.PipUI</item> <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item> <item>@string/config_systemUIVendorServiceComponent</item> <item>com.android.systemui.util.leak.GarbageMonitor$Service</item> <item>com.android.systemui.LatencyTester</item> <item>com.android.systemui.globalactions.GlobalActionsComponent</item> <item>com.android.systemui.ScreenDecorations</item> <item>com.android.systemui.fingerprint.FingerprintDialogImpl</item> <item>com.android.systemui.SliceBroadcastRelayHandler</item> </string-array> 

Lautstärkeregler


Wir verwenden regelmäßig die Lautstärketasten auf unseren Geräten, denken jedoch nicht darüber nach, welche Prozesse im System ablaufen sollen, damit wir den Ton erhöhen oder verringern können. Die Bedienung scheint in Worten recht einfach zu sein, aber wenn Sie sich die VolumeUI ansehen, die sich in einem Unterordner von SystenUI / volume befindet , hat die Benutzeroberfläche ihre eigenen Variationen in verschiedenen Modi.


Ich habe bereits gesagt, dass SystemUI-Dienste mit der Methode start () gestartet werden. Wenn wir uns die VolumeUI- Klasse ansehen , erbt sie auch von SystemUI.

 public class VolumeUI extends SystemUI { private static final String TAG = "VolumeUI"; private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG); private final Handler mHandler = new Handler(); private boolean mEnabled; private VolumeDialogComponent mVolumeComponent; @Override public void start() { boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui); boolean enableSafetyWarning = mContext.getResources().getBoolean(R.bool.enable_safety_warning); mEnabled = enableVolumeUi || enableSafetyWarning; if (!mEnabled) return; mVolumeComponent = new VolumeDialogComponent(this, mContext, null); mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning); putComponent(VolumeComponent.class, getVolumeComponent()); setDefaultVolumeController(); } … 

Hier sehen wir, dass wir mit mEnabled festlegen, ob wir ein Panel mit Soundeinstellungen anzeigen möchten. Nach der VolumeDialogComponent zeigt VolumeUI die Soundbar als Dialogfeld an. Alle Aktionen zum Drücken der Lautstärketasten werden jedoch in PhoneWindow ausgeführt .

  protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) { ... switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_MUTE: { // If we have a session send it the volume command, otherwise // use the suggested stream. if (mMediaController != null) { mMediaController.dispatchVolumeButtonEventAsSystemService(event); } else { getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(event, mVolumeControlStreamType); } return true; } ... protected boolean onKeyUp(int featureId, int keyCode, KeyEvent event) { final KeyEvent.DispatcherState dispatcher = mDecor != null ? mDecor.getKeyDispatcherState() : null; if (dispatcher != null) { dispatcher.handleUpEvent(event); } //Log.i(TAG, "Key up: repeat=" + event.getRepeatCount() // + " flags=0x" + Integer.toHexString(event.getFlags())); switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: { // If we have a session send it the volume command, otherwise // use the suggested stream. if (mMediaController != null) { mMediaController.dispatchVolumeButtonEventAsSystemService(event); } else { getMediaSessionManager().dispatchVolumeKeyEventAsSystemService( event, mVolumeControlStreamType); } return true; } … 

Soweit wir sehen können, wird KEYCODE_VOLUME_UP (+) nicht verarbeitet und geht in die Verarbeitung von KEYCODE_VOLUME_DOWN (-) über. In beiden Fällen, sowohl onKeyDown als auch onKeyUp, wird die dispatchVolumeButtonEventAsSystemService- Methode aufgerufen.

  public void dispatchVolumeButtonEventAsSystemService(@NonNull KeyEvent keyEvent) { switch (keyEvent.getAction()) { case KeyEvent.ACTION_DOWN: { int direction = 0; switch (keyEvent.getKeyCode()) { case KeyEvent.KEYCODE_VOLUME_UP: direction = AudioManager.ADJUST_RAISE; break; ... mSessionBinder.adjustVolume(mContext.getPackageName(), mCbStub, true, direction, ... } 

Hier rufen wir also die adjustVolume-Methode auf, damit wir unsere Richtung überprüfen können, der der Ereignisparameter zugewiesen wird.

Wenn wir zum AudioService gelangen, wird sendVolumeUpdate aufgerufen, und zusätzlich zum Aufrufen der postVolumeChanged-Methode wird die HDMI-Schnittstelle installiert.

  // UI update and Broadcast Intent protected void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) { ... mVolumeController.postVolumeChanged(streamType, flags); } private int updateFlagsForSystemAudio(int flags) { ... if (mHdmiSystemAudioSupported && ((flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0)) { flags &= ~AudioManager.FLAG_SHOW_UI; } ... } return flags; } public void postVolumeChanged(int streamType, int flags) { ... mController.volumeChanged(streamType, flags); ... } 

Klingelton-Player


RingtonePlayer in Android fungiert als Player. Es erbt auch von SystemUI und in der start () -Methode sehen wir:

  @Override public void start() { ... mAudioService.setRingtonePlayer(mCallback); ... } 

Hier installieren wir mCallback, eine Instanz von IRingtonePlayer .

 private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() { @Override public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping) throws RemoteException { ... } @Override public void stop(IBinder token) { ... } @Override public boolean isPlaying(IBinder token) { ... } @Override public void setPlaybackProperties(IBinder token, float volume, boolean looping) { ... } @Override public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) { ... } @Override public void stopAsync() { ... } @Override public String getTitle(Uri uri) { ... } @Override public ParcelFileDescriptor openRingtone(Uri uri) { ... } }; 

Am Ende können Sie RingtonePlayerService mithilfe des Binders zum Abspielen von Audiodateien steuern.

Powerui


PowerUI ist für die Energieverwaltung und Benachrichtigungen verantwortlich. Es wird ebenfalls von SystemUI geerbt und verfügt über eine start () -Methode.

 public void start() { mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mHardwarePropertiesManager = (HardwarePropertiesManager) mContext.getSystemService(Context.HARDWARE_PROPERTIES_SERVICE); mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime(); mWarnings = Dependency.get(WarningsUI.class); mEnhancedEstimates = Dependency.get(EnhancedEstimates.class); mLastConfiguration.setTo(mContext.getResources().getConfiguration()); ContentObserver obs = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { updateBatteryWarningLevels(); } }; final ContentResolver resolver = mContext.getContentResolver(); resolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL), false, obs, UserHandle.USER_ALL); updateBatteryWarningLevels(); mReceiver.init(); showThermalShutdownDialog(); initTemperatureWarning(); } 

Wie aus dem obigen Code ersichtlich ist, werden die Änderungen von Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL abonniert, und danach wird der Aufruf von mReceiver.init () aufgerufen.

  public void init() { // Register for Intent broadcasts for... IntentFilter filter = new IntentFilter(); filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); filter.addAction(Intent.ACTION_BATTERY_CHANGED); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_USER_SWITCHED); mContext.registerReceiver(this, filter, null, mHandler); } 

Hier wird der Rundfunkempfänger registriert, mit dessen Hilfe die Änderungsverfolgung durchgeführt wird.

Die Aufgaben


Recents ist eine primäre und häufig verwendete Funktion in Android-Mobilgeräten.

Die Hauptfunktionen:


  • Alle Aufgaben anzeigen
  • Zwischen Aufgaben wechseln
  • Aufgaben löschen


Darüber hinaus wird Recents auch von SystemUI geerbt. RecentsActivity erstellt und aktualisiert die neuesten Aufgaben, damit wir sie auf unserem Bildschirm sehen können.


Und mit RecentTaskInfo können wir Informationen über eine bestimmte Aufgabe erhalten.

 public static class RecentTaskInfo implements Parcelable { public int id; public int persistentId; public Intent baseIntent; public ComponentName origActivity; public ComponentName realActivity; public CharSequence description; public int stackId; ... 

Im Allgemeinen können laufende Aufgaben in einem separaten Thema platziert werden. Ich habe es von allen Seiten studiert, weil ich den Anwendungsbildschirm verwischen wollte, bevor ich die Anwendung in den Hintergrund schaltete, damit in RecentsTask eine unlesbare Version des Snapshots angezeigt wurde. Das Problem ist jedoch, dass der Anwendungs-Snapshot erstellt wird, bevor onPause () aufgerufen wird. Dieses Problem kann auf verschiedene Arten gelöst werden. Oder setzen Sie das Flag so, dass das System einfach den Inhalt des Bildschirms mit verbirgt

 getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); 

Worüber ich in einem früheren Artikel nur über Schnappschüsse gesprochen habe.

Sie können im Allgemeinen sicherstellen, dass die spezifische Aktivität der Anwendung nicht in Aufgaben angezeigt wird, indem Sie sie im Manifest ablegen

 android:excludeFromRecents = "true" 

Oder Sie können den Trick mit verwenden

 Intent.FLAG_ACTIVITY_MULTIPLE_TASK 

Sie können die Hauptaktivität über dem Flag excludeFromRecents = true setzen , damit sich der Bildschirm nicht in den ausgeführten Aufgaben befindet. Wenn die Anwendung geladen wird, führen Sie eine separate Aufgabe aus, die entweder einen verschwommenen Screenshot der Hauptaktivität oder ein anderes Bild anzeigt. Wie dies getan werden kann, wird in der offiziellen Dokumentation am Beispiel von Google Drive beschrieben.

Bildschirm sperren


Keyguard ist bereits komplizierter als alle oben genannten Module. Es ist ein Dienst, der in SystemUI ausgeführt und mit KeyguardViewMediator verwaltet wird.

 private void setupLocked() { ... // Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard // is disabled. if (mContext.getResources().getBoolean( com.android.keyguard.R.bool.config_enableKeyguardService)) { setShowingLocked(!shouldWaitForProvisioning() && !mLockPatternUtils.isLockScreenDisabled( KeyguardUpdateMonitor.getCurrentUser()), mAodShowing, mSecondaryDisplayShowing, true /* forceCallbacks */); } else { // The system's keyguard is disabled or missing. setShowingLocked(false, mAodShowing, mSecondaryDisplayShowing, true); } ... mLockSounds = new SoundPool(1, AudioManager.STREAM_SYSTEM, 0); String soundPath = Settings.Global.getString(cr, Settings.Global.LOCK_SOUND); if (soundPath != null) { mLockSoundId = mLockSounds.load(soundPath, 1); } ... int lockSoundDefaultAttenuation = mContext.getResources().getInteger( com.android.internal.R.integer.config_lockSoundVolumeDb); mLockSoundVolume = (float)Math.pow(10, (float)lockSoundDefaultAttenuation/20); ... } 

In der Realität arbeitet KeyguardService jedoch nicht unabhängig mit der Sperrbildschirmschnittstelle, sondern überträgt nur Informationen an das StatusBar-Modul, in dem bereits Maßnahmen hinsichtlich des visuellen Erscheinungsbilds des Bildschirms und der Anzeige von Informationen ergriffen wurden.

Benachrichtigungsleiste


SystemBars hat ein ziemlich komplexes Gerät und eine ziemlich komplexe Struktur. Seine Arbeit gliedert sich in zwei Phasen:
  1. SystemBars initialisieren
  2. Benachrichtigungen anzeigen

Wenn Sie sich den Start von SystemBars ansehen

 private void createStatusBarFromConfig() { ... final String clsName = mContext.getString(R.string.config_statusBarComponent); ... cls = mContext.getClassLoader().loadClass(clsName); ... mStatusBar = (SystemUI) cls.newInstance(); ... } 

Dann sehen wir einen Link zu der Ressource, aus der der Klassenname gelesen und eine Instanz erstellt wird.

 <string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.StatusBar</string> 

Wir sehen also, dass hier eine Statusleiste aufgerufen wird, die mit der Benachrichtigungs- und UI-Ausgabe funktioniert.

Ich denke, niemand hat daran gezweifelt, dass Android sehr kompliziert ist und viele Tricks enthält, die in einer Vielzahl von Codezeilen beschrieben werden. SystemUI ist einer der wichtigsten Teile dieses Systems und ich habe es genossen, darüber zu lernen. Aufgrund der Tatsache, dass es zu diesem Thema nur sehr wenig Material gibt, korrigieren Sie mich bitte, wenn Sie Fehler bemerken.

PS Ich stelle immer eine Auswahl an Material und kürzeren Artikeln auf @paradisecurity in Telegrammen zur Verfügung.

Source: https://habr.com/ru/post/de433620/


All Articles