
Neste artigo, analisarei a arquitetura e o princípio de operação do principal aplicativo Android - SystemUI. Eu estava interessado neste tópico, porque me pergunto como o sistema funciona, que é usado por um número tão grande de usuários e para o qual milhares de aplicativos são baixados no Google Play todos os dias ou simplesmente na Internet. Além disso, estou interessado na questão de segurança da informação do Android e nos aplicativos criados para ele.
No Android, o SystemUI é um aplicativo cujo caminho do código-fonte está em
platform_frameworks_base / packages / SystemUI / , no dispositivo em system / priv-app / -SystemUI.
O aplicativo privado é o diretório em que os aplicativos privilegiados são armazenados. A propósito, no caminho do sistema / aplicativo, existem aplicativos pré-instalados e os aplicativos comuns que instalamos em nosso dispositivo são armazenados em dados / aplicativo.
Isso imediatamente levanta a questão: por que todos os aplicativos pré-instalados e privilegiados não podem ser colocados em um diretório, por que essa separação é necessária?
O fato é que alguns aplicativos são mais sistêmicos do que outros :) E essa separação é necessária para reduzir a cobertura de exploração dos aplicativos do sistema para obter acesso a operações protegidas. Você pode criar um aplicativo que terá um ApplicationInfo.FLAG_SYSTEM especial e no sistema obterá mais direitos; no entanto, um arquivo apk com essa permissão será colocado na seção do sistema.
Portanto, SystemUI é um arquivo apk, que é essencialmente um aplicativo normal. No entanto, se você olhar para o dispositivo SystemUI complexo, ele deixa de parecer que é apenas um aplicativo simples, certo?
Esta aplicação executa funções muito importantes:
- Navegação
- Aplicações recentes
- Configurações rápidas
- Barra de notificação
- Tela de bloqueio
- Controle de volume
- Tela inicial
- ...
Iniciar o SystemUI
Como eu disse acima, o SystemUI não é como um aplicativo comum; portanto, seu lançamento não é acompanhado pelo lançamento de atividades, como é o caso da maioria dos aplicativos. SystemUI é uma
interface de usuário global que inicia durante o processo de inicialização do sistema e não pode ser concluída.
<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">
Se entrarmos no SystemServer, que é um dos dois pilares do mundo Android (o segundo é o Zygote, mas falarei sobre isso em outro momento), podemos encontrar o local em que o
SystemUI inicia quando o sistema é inicializado.
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);
Aqui vemos como o serviço SystemUI começa a usar a API não pública startServiceAsUser. Se você quisesse usar isso, teria que recorrer à reflexão. Mas se você decidir usar a API de reflexão no Android, pense algumas vezes se vale a pena.
Pense cem vezes :)Portanto, aqui criamos um processo separado para o aplicativo e, de fato, cada seção do
SystemUI é um serviço separado ou um módulo independente.
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); } }
O método start () é chamado para iniciar
cada serviço , listado abaixo.
<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>
Controle de volume
Usamos regularmente os botões de volume em nossos dispositivos, mas não pensamos em quais processos devem ocorrer no sistema para aumentar ou diminuir o som. A operação parece bastante simples em palavras, mas se você observar o VolumeUI, localizado em uma subpasta do
SystenUI / volume , a interface
terá sua própria variação em diferentes modos.

Eu já disse que os serviços SystemUI são iniciados pelo método start (). Se olharmos para a classe
VolumeUI , ela também herda da 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(); } …
Aqui vemos que, com o mEnabled, determinamos se devemos nos mostrar um painel com configurações de som. E, a julgar pelo VolumeDialogComponent, o VolumeUI exibe a barra de som como uma caixa de diálogo. Mas todas as ações relacionadas ao pressionar as teclas de volume são processadas no
PhoneWindow .
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: {
Até onde podemos ver, KEYCODE_VOLUME_UP (+) não é processado e entrará no processamento de KEYCODE_VOLUME_DOWN (-). Nos dois eventos, onKeyDown e onKeyUp, o método
dispatchVolumeButtonEventAsSystemService é chamado.
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, ... }
Então, aqui chamamos o método AdjustVolume, para que possamos verificar nossa direção à qual o parâmetro do evento será atribuído.
Como resultado, quando chegarmos ao
AudioService , onde
sendVolumeUpdate será chamado, onde, além de chamar o método postVolumeChanged, a interface HDMI será instalada.
Ringtoneplayer
O RingtonePlayer no Android atua como jogador. Também herda de SystemUI e no método
start () vemos:
@Override public void start() { ... mAudioService.setRingtonePlayer(mCallback); ... }
Aqui instalamos o mCallback, que é essencialmente uma instância do
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) { ... } };
No final, você pode controlar o RingtonePlayerService usando o Fichário para reproduzir arquivos de som.
Powerui
O PowerUI é responsável pelo gerenciamento de energia e pelas notificações. Também é herdado do SystemUI e possui um método start ().
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(); }
Como podemos ver no código acima, as alterações Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL são assinadas e, depois disso, a chamada para
mReceiver.init () é chamada.
public void init() {
Aqui o receptor de transmissão é registrado, com a ajuda da qual o rastreamento de alterações é realizado.
As tarefas
Recentes é um recurso primário e frequentemente usado em dispositivos móveis Android.
As principais funções:
- Exibir todas as tarefas
- Alternar entre tarefas
- Exclusão de tarefas
Além disso, Recentes também é herdado do SystemUI. RecentsActivity cria e atualiza as tarefas mais recentes para que possamos vê-las em nossa tela.

E, usando o
RecentTaskInfo , podemos obter informações sobre uma tarefa específica.
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; ...
Em geral, as tarefas em execução podem ser colocadas em um tópico separado. Estudei-o de todos os lados, porque queria desfocar a tela do aplicativo antes de alterná-lo para segundo plano, para que uma versão ilegível do instantâneo fosse exibida no RecentsTask. No entanto, o problema é que a captura instantânea do aplicativo é feita antes da onPause () ser chamada. Este problema pode ser resolvido de várias maneiras. Ou defina o sinalizador para que o sistema simplesmente oculte o conteúdo da tela com
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
O que eu falei em um
artigo anterior sobre apenas instantâneos.
Geralmente, você pode garantir que a atividade específica do aplicativo não apareça nas tarefas, colocando no manifesto
android:excludeFromRecents = "true"
Ou você pode usar o truque com
Intent.FLAG_ACTIVITY_MULTIPLE_TASK
Você pode definir a atividade principal acima do sinalizador
excludeFromRecents = true , para que sua tela não esteja nas tarefas em execução, mas quando o aplicativo carregar, execute uma tarefa separada que mostrará uma captura de tela borrada da atividade principal ou qualquer outra imagem. Mais detalhadamente, como isso pode ser feito está descrito na
documentação oficial do exemplo do Google Drive.
Tela de bloqueio
O Keyguard já é mais complicado do que todos os módulos acima. É um serviço que é executado no SystemUI e é gerenciado usando o KeyguardViewMediaor.
private void setupLocked() { ...
No entanto, na realidade, o KeyguardService não funciona de forma independente com a interface da tela de bloqueio, apenas transfere informações para o módulo StatusBar, onde ações já são tomadas em relação à aparência visual da tela e à exibição de informações.
Barra de notificação
O SystemBars possui um dispositivo e estrutura bastante complexos. Seu trabalho é dividido em duas etapas:
- Inicializando SystemBars
- Exibir notificações
Se você observar o lançamento do SystemBars
private void createStatusBarFromConfig() { ... final String clsName = mContext.getString(R.string.config_statusBarComponent); ... cls = mContext.getClassLoader().loadClass(clsName); ... mStatusBar = (SystemUI) cls.newInstance(); ... }
Em seguida, vemos um link para o recurso a partir do qual
o nome da classe é lido e uma instância é criada.
<string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.StatusBar</string>
Portanto, vemos que uma StatusBar é chamada aqui, que funcionará com a notificação e a saída da interface do usuário.
Acho que ninguém duvidou que o Android seja muito complicado e contenha muitos truques descritos em um grande número de linhas de código. O SystemUI é uma das partes mais importantes deste sistema e eu gostei de aprender sobre ele. Devido ao fato de haver muito pouco material sobre esse tópico, se você encontrar algum erro, corrija-me.
PS: Eu sempre exponho a seleção de materiais e artigos mais curtos sobre o
@paradisecurity em telegramas.