Compatibilité des Dialogues Android et Logique de Sécurité Mobile

Gestion des Styles de Dialogue sur Anciennes Versions

Problématiques Identifiées

Lors du développement d'interfaces utilisateur avec des boîtes de dialogue, deux anomalies apparaissent sur les API inférieures :

  • Le fond perd sa transparence et affiche une couleur opaque indésirable
  • Un padding interne systématique encadre le contenu, réduisant l'espace utilisable

Architecture du Module de Sécurité Mobile

Flux de Navigation Post-Authentification

Après validation du mot de passe, l'application détermine la destination selon l'état de configuration :

// Structure décisionnelle simplifiée
if (configurationTerminee) {
    lancerEcranRecapitulatif();
} else {
    initialiserAssistantConfiguration();
}

L'assistant comporte quatre étapes obligatoires :

  1. Présentation : découverte des fonctionnalités de protection
  2. Association carte SIM : enregistrement de l'identifiant matériel
  3. Contact de secours : saisie manuelle ou sélection depuis le répertoire
  4. Activation globale : interrupteur principal de surveillance

Écran de Synthèse

Une fois la configuration validée, l'interface affiche :

  • Le numéro de contact enregistré
  • L'état actuel de la protection (active/inactive)
  • Un accès à la réinitialisation des paramètres

Implémentation des Interfaces Guidées

Structure XML de la Première Vue

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <TextView
        style="@style/EnTeteGuide"
        android:text="@string/titre_bienvenue"/>
        
    <TextView
        android:text="@string/sous_titre_fonctionnalites"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:layout_margin="12dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    
    <!-- Liste des capacités avec icônes étoile -->
    <TextView
        android:text="@string/alerte_changement_sim"
        android:drawableStart="@android:drawable/star_big_on"
        android:gravity="center_vertical"
        style="@style/ElementListeCapacite"/>
        
    <TextView
        android:text="@string/localisation_gps"
        android:drawableStart="@android:drawable/star_big_on"
        style="@style/ElementListeCapacite"/>
        
    <TextView
        android:text="@string/suppression_distante"
        android:drawableStart="@android:drawable/star_big_on"
        style="@style/ElementListeCapacite"/>
        
    <TextView
        android:text="@string/verrouillage_distant"
        android:drawableStart="@android:drawable/star_big_on"
        style="@style/ElementListeCapacite"/>
        
</LinearLayout>

Indicateurs de Progresion Visuelle

Quatre points représentent l'avancement dans l'assistant. Le point actif adopte l'icône presence_online, les autres presence_invisible.

Sélecteurs d'État et Ressources Adaptables

Principe des Drawable Sélecteurs

Pour le bouton de navigation, l'apparence change selon l'état tactile :

<!-- res/drawable/selecteur_bouton_navigation.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" 
          android:drawable="@drawable/bouton_fonce"/>
    <item android:drawable="@drawable/bouton_clair"/>
</selector>

Intégration dans la Disposition

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <ImageView
        android:src="@drawable/illustration_etape"
        android:layout_centerInParent="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    
    <Button
        android:text="@string/suivant"
        android:onClick="passerEtapeSuivante"
        android:layout_alignParentEnd="true"
        android:layout_alignParentBottom="true"
        android:background="@drawable/selecteur_bouton_navigation"
        android:drawableEnd="@drawable/fleche_droite"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
        
</RelativeLayout>

Optimisation avec Images Nine-Patch

Les ressources .9.png permettent de définir :

  • Zone extensible : marquée par les bordures noires supérieure et gauche
  • Zone de contenu : délimitée par les bordures inférieure et droite

L'outil draw9patch facilite la création en visualisant les zones éditables (hachures rouges).

Factorisation des Styles de Navigation

Définition Réutilisable

<!-- res/values/styles.xml -->
<style name="BoutonNavigation.Precedent">
    <item name="android:text">@string/retour</item>
    <item name="android:onClick">retournerEtapePrecedente</item>
    <item name="android:layout_alignParentStart">true</item>
    <item name="android:layout_alignParentBottom">true</item>
    <item name="android:drawableStart">@drawable/fleche_gauche</item>
    <item name="android:background">@drawable/selecteur_bouton_navigation</item>
    <item name="android:layout_width">wrap_content</item>
    <item name="android:layout_height">wrap_content</item>
</style>

<style name="BoutonNavigation.Suivant">
    <item name="android:text">@string/suivant</item>
    <item name="android:onClick">avancerEtapeSuivante</item>
    <item name="android:layout_alignParentEnd">true</item>
    <item name="android:layout_alignParentBottom">true</item>
    <item name="android:drawableEnd">@drawable/fleche_droite</item>
    <item name="android:background">@drawable/selecteur_bouton_navigation</item>
    <item name="android:layout_width">wrap_content</item>
    <item name="android:layout_height">wrap_content</item>
</style>

Interface d'Association Carte SIM

Composant Personnalisé

<com.securite.mobile.vue.ElementParametrage
    android:id="@+id/ep_associer_sim"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:titre="Associer la carte SIM"
    app:descriptionInactif="Aucune carte liée"
    app:descriptionActif="Carte SIM enregistrée"/>

Logique de Persistance

public class EtapeAssociationSim extends ActiviteGuide {
    private ElementParametrage epAssocierSim;
    private GestionnaireTelephonie gestionnaireTel;
    
    @Override
    protected void initialiserInterface() {
        epAssocierSim = findViewById(R.id.ep_associer_sim);
        gestionnaireTel = (GestionnaireTelephonie) getSystemService(Context.TELEPHONY_SERVICE);
        
        restaurerEtatPrecedent();
        configurerEcouteurInteraction();
    }
    
    private void restaurerEtatPrecedent() {
        String identifiantSim = PreferencesSecurite.lireChaine(this, 
            Constantes.CLE_ID_SIM, "");
        epAssocierSim.setActif(!identifiantSim.isEmpty());
    }
    
    private void configurerEcouteurInteraction() {
        epAssocierSim.setOnClickListener(v -> {
            boolean etatActuel = epAssocierSim.estActif();
            boolean nouvelEtat = !etatActuel;
            epAssocierSim.setActif(nouvelEtat);
            
            if (nouvelEtat) {
                String numeroSerie = gestionnaireTel.getSimSerialNumber();
                PreferencesSecurite.ecrireChaine(getApplicationContext(), 
                    Constantes.CLE_ID_SIM, numeroSerie);
            } else {
                PreferencesSecurite.supprimer(getApplicationContext(), 
                    Constantes.CLE_ID_SIM);
            }
        });
    }
}

Sélection du Contact de Confiance

Requête sur le Fournisseur de Contenu

private List<Map<String, String>> extraireContacts() {
    List<Map<String, String>> listeContacts = new ArrayList<>();
    
    Uri uriContacts = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
    String[] projections = {
        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
        ContactsContract.CommonDataKinds.Phone.NUMBER
    };
    
    try (Cursor curseur = getContentResolver().query(uriContacts, 
            projections, null, null, null)) {
        
        while (curseur != null && curseur.moveToNext()) {
            Map<String, String> contact = new HashMap<>();
            contact.put("nom", curseur.getString(0));
            contact.put("numero", curseur.getString(1).replace("-", ""));
            listeContacts.add(contact);
        }
    }
    
    return listeContacts;
}

Transmission du Résultat

// Dans l'activité de sélection
listeVue.setOnItemClickListener((parent, vue, position, id) -> {
    Map<String, String> contactSelectionne = adaptateur.getItem(position);
    Intent resultat = new Intent();
    resultat.putExtra("numero_urgence", contactSelectionne.get("numero"));
    setResult(RESULT_OK, resultat);
    finish();
});

// Dans l'activité appelante
@Override
protected void onActivityResult(int codeRequete, int codeResultat, Intent donnees) {
    super.onActivityResult(codeRequete, codeResultat, donnees);
    
    if (codeResultat == RESULT_OK && donnees != null) {
        String numero = donnees.getStringExtra("numero_urgence");
        champTelephone.setText(numero);
        PreferencesSecurite.ecrireChaine(this, 
            Constantes.CLE_NUMERO_URGENCE, numero);
    }
}

Activation Finale et Persistance

Écran de Confirmation

<CheckBox
    android:id="@+id/cb_protection_active"
    android:text="@string/message_protection_inactive"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
public class EtapeFinalisation extends ActiviteGuide {
    private CheckBox cbProtection;
    
    @Override
    protected void initialiserInterface() {
        cbProtection = findViewById(R.id.cb_protection_active);
        
        boolean protectionActivee = PreferencesSecurite.lireBooléen(this, 
            Constantes.CLE_PROTECTION_ACTIVEE, false);
        cbProtection.setChecked(protectionActivee);
        mettreAJourLibelle(protectionActivee);
        
        cbProtection.setOnCheckedChangeListener((bouton, estCoche) -> {
            mettreAJourLibelle(estCoche);
            PreferencesSecurite.ecrireBooléen(getApplicationContext(), 
                Constantes.CLE_PROTECTION_ACTIVEE, estCoche);
            PreferencesSecurite.ecrireBooléen(getApplicationContext(), 
                Constantes.CLE_CONFIGURATION_TERMINEE, estCoche);
        });
    }
    
    private void mettreAJourLibelle(boolean active) {
        cbProtection.setText(active ? 
            R.string.message_protection_active : 
            R.string.message_protection_inactive);
    }
}

Transitions Animées entre Vues

Ressources d'Animation

<!-- res/anim/glisser_entree_droite.xml -->
<translate
    android:fromXDelta="100%p"
    android:toXDelta="0"
    android:duration="300"/>

<!-- res/anim/glisser_sortie_gauche.xml -->
<translate
    android:fromXDelta="0"
    android:toXDelta="-100%p"
    android:duration="300"/>

Application Programmatique

public void avancerEtapeSuivante() {
    Intent intention = new Intent(this, etapeSuivante);
    startActivity(intention);
    overridePendingTransition(R.anim.glisser_entree_droite, 
                              R.anim.glisser_sortie_gauche);
}

Étiquettes: Android TelephonyManager SharedPreferences ContentProvider Nine-Patch

Publié le 28 août à 21h20