🤖 Unit 3 • Android Architecture & Artifacts

Android Project Structure: Manifest, Resources & Gradle

A comprehensive deep dive into Android's project anatomy and build architecture: The AndroidManifest.xml contract and component registration, source directory partitioning (main, test, androidTest), the resource subsystem (drawable, layout, values), automated R.java class generation via AAPT2, Gradle build configurations (Project vs. Module DSL), Build Variants and Product Flavors, Debug vs. Release R8 minification, Application ID vs. Versioning, and the runtime permissions security model.

Curriculum: Mobile Software Engineering (Android Track)
Unit: 03 of 15
Prerequisite: Unit 01 (Intro to Android), Unit 02 (Kotlin)
Target: Android 14/15 (API 34/35)
01

AndroidManifest.xml: The Application Operating Contract

📜 The Universal Blueprint of an Android App

The AndroidManifest.xml file (located at app/src/main/AndroidManifest.xml) is the definitive metadata document read by the Android OS Package Manager during installation and runtime launch. Every Android app must possess exactly one manifest file:

  • Unique Identity: Declares the package namespace matching Gradle's build config.
  • Component Registration: All four core application components (<activity>, <service>, <receiver>, <provider>) must be declared. Unregistered components are invisible to the OS and crash upon intent resolution.
  • Permissions: Declares system capabilities required via <uses-permission>.
  • Hardware Capabilities: Filters device compatibility on Google Play via <uses-feature android:name="android.hardware.camera" android:required="false" />.
  • The android:exported Directive (Android 12+): Any component containing an <intent-filter> must explicitly declare android:exported="true" (if other apps/system can launch it) or android:exported="false" (if private). Failing to declare this causes installation build failure!
AndroidManifest.xml — Production Architecture
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <!-- 1. Hardware & Network Permissions -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-feature android:name="android.hardware.camera" android:required="false" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.DevAscent">

        <!-- 2. Primary Entry-Point Launcher Activity -->
        <activity
            android:name=".ui.MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- 3. Private Internal Detail Activity -->
        <activity
            android:name=".ui.DetailActivity"
            android:exported="false" />
    </application>

</manifest>
02

Source Directory Partitioning & The Resource Subsystem

📁 Source Directory Triad: main, test & androidTest

The app/src/ directory is partitioned into three isolated compilation roots:

  • src/main/: Contains production application assets, the manifest, and Kotlin/Java source code.
  • src/test/ (Local Unit Tests): Pure JVM unit tests executed directly on your development computer's JVM without requiring an Android emulator or device (fast, mock-driven via Mockito/Robolectric).
  • src/androidTest/ (Instrumented Tests): Tests compiled into an auxiliary test APK that runs on an actual Android device or AVD emulator, executing against real hardware and Android OS APIs (e.g., Espresso UI tests).
🎨 The Resource Directory Architecture (res/)

Resources are non-code assets decoupled from programming logic to allow dynamic adaptation to screen densities, orientations, and local languages:

Resource Folder Contained Asset Types Adaptive Resolution Mechanism
res/drawable/ Vector Drawables (XML), Bitmaps (PNG, JPG), Nine-Patch assets Density qualifiers: drawable-mdpi (1x), hdpi (1.5x), xhdpi (2x), xxhdpi (3x), xxxhdpi (4x)
res/layout/ XML UI layout hierarchies (ConstraintLayout, LinearLayout) Screen qualifiers: layout-land (horizontal landscape), layout-sw600dp (tablets)
res/values/ Text strings, color tokens, dimensions (dp/sp), styles, and themes Locale qualifiers: values-es (Spanish), values-night (Dark Mode theme)
res/mipmap/ Application launcher icons only Density-specific icons preserved during APK density stripping
03

Values Resources (Strings, Colors, Themes) & The R Class

💬 1. strings.xml (Localization / i18n)

Decouples UI text from source code, enabling seamless multilingual translation:

res/values/strings.xml
<resources>
    <string name="app_name">DevAscent Academy</string>
    <string name="welcome_banner">Welcome back, %1$s!</string>
    <plurals name="items_count">
        <item quantity="one">%d Lesson Pending</item>
        <item quantity="other">%d Lessons Pending</item>
    </plurals>
</resources>
🎨 2. colors.xml (Color Design Tokens)

Defines hex color tokens used across layouts and vector drawables:

res/values/colors.xml
<resources>
    <color name="emerald_500">#10B981</color>
    <color name="emerald_700">#047857</color>
    <color name="slate_900">#0F172A</color>
    <color name="surface_dark">#111726</color>
    <color name="translucent_overlay">#80000000</color>
</resources>
⚡ How the Generated R Class Bridges XML and Kotlin/Java

During build time, the Android Asset Packaging Tool (AAPT2) scans the entire res/ directory and dynamically compiles a synthetic Java class named R.
Every resource is assigned a unique static 32-bit hexadecimal integer ID:

  • In Kotlin/Java code: Access via R.string.app_name, R.layout.activity_main, R.id.btn_submit.
  • In XML layouts: Access via @string/app_name, @color/emerald_500, @drawable/ic_logo.
Visual Architecture: Resource Compilation & R.java ID Generation Pipeline
Raw XML / Assets
strings.xml, layout.xml, ic_logo.xml
➔ Processed by ➔
AAPT2 Compiler
Validates XML syntax & parses IDs
➔ Generates ➔
Generated R Class
R.id.btn = 0x7f080001
Direct Static Ints
04

Gradle Build Variants: Build Types & Product Flavors

🏭 The Build Variant Matrix Equation

In professional mobile engineering, a single codebase must generate different versions of an application (e.g., Free vs. Paid, Staging vs. Production, QA vs. Release).
Gradle constructs Build Variants using the Cartesian product formula:

Build Variant = Product Flavor × Build Type

  • Build Types: Configure how the package is built (e.g., debug vs. release). Controls code optimization, minification, logging, and cryptographic signing keys.
  • Product Flavors: Configure what features are packaged (e.g., free with ads vs. paid with offline sync; or staging pointing to dev servers vs. production pointing to live AWS servers).
Engineering Property Debug Build Type Release Build Type
Cryptographic Signing Signed with auto-generated generic debug.keystore Signed with private, secured company production Keystore
Code Minification & Shrinking Disabled (isMinifyEnabled = false) for fast compile Enabled (R8 / ProGuard): Strips unused classes & obfuscates symbols
Debuggable Flag android:debuggable="true" (JDWP debugger can attach) android:debuggable="false" (Debugger blocked for security)
Application ID Suffix Commonly appends .debug (permits co-installation on same device) Original root applicationId (e.g., com.devascent.app)
app/build.gradle.kts — Flavors & Build Types Configuration
android {
    namespace = "com.devascent.academy"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.devascent.academy"
        minSdk = 24
        targetSdk = 34
        versionCode = 101          // Monotonically increasing integer for Google Play updates
        versionName = "1.0.1"      // Semantic version visible to users
    }

    flavorDimensions += "tier"
    productFlavors {
        create("dev") {
            dimension = "tier"
            applicationIdSuffix = ".dev"
            versionNameSuffix = "-dev"
        }
        create("prod") {
            dimension = "tier"
        }
    }

    buildTypes {
        debug {
            applicationIdSuffix = ".debug"
            isDebuggable = true
        }
        release {
            isMinifyEnabled = true
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
}
05

Application ID vs. Package Name & App Versioning

🆔 Application ID vs. Source Package Name

In modern Android development, the Application ID and the source code Package Name are completely decoupled:

  • applicationId (in build.gradle.kts): The globally unique identifier of your app on Google Play and on device filesystems (e.g., com.devascent.academy). The OS uses this ID to isolate the app sandbox. Once published to Google Play, the applicationId can never be changed!
  • namespace (or Java package): The Kotlin/Java package hierarchy used by developers to organize source files and determine where the R class is generated.

Versioning Contract:
• versionCode (Integer): An internal integer evaluated by Google Play to enforce upgrade sequence (e.g., version 102 replaces 101). Cannot be downgraded!
• versionName (String): A user-facing semantic version string (e.g., "2.4.0-beta").

06

Android Permissions Architecture: Normal vs. Dangerous

🛡️ The Android Permission Security Model

To safeguard user privacy, Android restricts access to sensitive hardware sensors, network radios, and user data through a multi-tier permission hierarchy:

🟢 1. Normal (Install-Time) Permissions

Permissions that pose minimal risk to user privacy or device operation:

  • Examples: android.permission.INTERNET, ACCESS_NETWORK_STATE, VIBRATE.
  • Approval Mechanism: Declared in AndroidManifest.xml; granted automatically by the OS at installation without prompting the user.
🔴 2. Dangerous (Runtime) Permissions

Permissions that access private user data or sensitive hardware:

  • Examples: ACCESS_FINE_LOCATION, CAMERA, RECORD_AUDIO, READ_CONTACTS.
  • Approval Mechanism: Must be declared in the manifest AND requested dynamically at runtime while the app is in use. Users can grant or revoke them at any time via system dialogs.
Visual Architecture: Android Dynamic Runtime Permission Request Flow
Check Permission
ContextCompat.checkSelfPermission()
➔ Granted? ➔
PERMISSION_GRANTED
Execute Camera / GPS Action
▼ If PERMISSION_DENIED ▼
Show UI Rationale
Explain WHY feature needs camera
➔ Request via ➔
System Permission Dialog
RequestPermission() Contract
07

Unit 3 Practical Capstone: Architecture Diagnostics & Runtime Permission Engine

🚀 Complete Production Permission & Resource Verifier

This complete Kotlin application demonstrates:
1. Querying string and color tokens from the compiled R class.
2. Inspecting BuildConfig metadata (Application ID, Version Code, Build Type).
3. Requesting runtime CAMERA permission using the modern AndroidX ActivityResultContracts API.

ArchitectureDiagnosticsActivity.kt — Complete Capstone Implementation
package com.devascent.academy.ui

import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.devascent.academy.R

class ArchitectureDiagnosticsActivity : AppCompatActivity() {
    private val tag = "DevAscent_Arch"
    private lateinit var statusLabel: TextView

    // Modern AndroidX Runtime Permission Request Contract
    private val cameraPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted: Boolean ->
        if (isGranted) {
            updateSecurityStatus("CAMERA ACCESS: GRANTED BY USER", true)
        } else {
            updateSecurityStatus("CAMERA ACCESS: DENIED BY USER", false)
            Toast.makeText(this, "Camera feature unavailable without permission", Toast.LENGTH_SHORT).show()
        }
    }

    @Override
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_diagnostics)

        statusLabel = findViewById(R.id.tv_security_status)
        val verifyButton: Button = findViewById(R.id.btn_verify_permission)

        // 1. Inspect R class Resource Strings
        val appName = getString(R.string.app_name)
        Log.i(tag, "Loaded resource string: $appName")

        // 2. Trigger Runtime Permission Flow
        verifyButton.setOnClickListener {
            checkAndRequestCameraPermission()
        }
    }

    private fun checkAndRequestCameraPermission() {
        val permission = Manifest.permission.CAMERA
        when {
            ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED -> {
                updateSecurityStatus("CAMERA ACCESS: ACTIVE (PREVIOUSLY GRANTED)", true)
            }
            shouldShowRequestPermissionRationale(permission) -> {
                Toast.makeText(this, "Camera is required to scan QR enrollment codes", Toast.LENGTH_LONG).show()
                cameraPermissionLauncher.launch(permission)
            }
            else -> {
                cameraPermissionLauncher.launch(permission)
            }
        }
    }

    private fun updateSecurityStatus(msg: String, isSuccess: Boolean) {
        statusLabel.text = msg
        val colorRes = if (isSuccess) R.color.emerald_500 else R.color.slate_900
        statusLabel.setTextColor(ContextCompat.getColor(this, colorRes))
        Log.d(tag, "Security Status Update: $msg")
    }
}
Logcat Diagnostics Output (ADB Execution Stream)
$ adb logcat -s DevAscent_Arch:V 10-15 20:25:01.412 18240 18240 I DevAscent_Arch: Loaded resource string: DevAscent Academy 10-15 20:25:04.810 18240 18240 D DevAscent_Arch: Requesting android.permission.CAMERA via ActivityResultLauncher 10-15 20:25:07.129 18240 18240 D DevAscent_Arch: Security Status Update: CAMERA ACCESS: GRANTED BY USER
08

University Exam & Technical Interview Viva Preparation Guide

Q1. What is the difference between applicationId and the Java/Kotlin package name?
The applicationId (defined in build.gradle.kts) is the definitive, immutable public identifier of your app published on Google Play and used by the Android OS to isolate process sandboxes. The package name (or namespace) represents the Java/Kotlin directory namespace used solely by developers to organize source files and generate the compiled R class. They can be identical, but Product Flavors frequently modify applicationId without altering source package structure.
Q2. What is the generated R class and what is the role of AAPT2?
AAPT2 (Android Asset Packaging Tool 2) parses and compiles all static XML resources, drawables, and strings in the res/ directory. During compilation, AAPT2 dynamically outputs a Java class named R containing nested static subclasses (e.g., R.string, R.id, R.layout). Each declared resource is assigned a unique 32-bit hexadecimal integer ID, enabling high-performance, type-safe resource binding from code without parsing XML at runtime.
Q3. Why was android:exported made mandatory in Android 12 (API 31)?
Prior to Android 12, components with <intent-filter> defaulted to being exported, leaving thousands of third-party apps unintentionally vulnerable to intent-spoofing and unauthorized launches by malicious sibling apps. Android 12 mandates an explicit android:exported="true" (if accessible to the OS/external apps) or android:exported="false" (if strictly internal) on any component declaring an intent filter to eliminate security ambiguity.
Q4. What is the difference between versionCode and versionName?
versionCode is a positive integer evaluated by Google Play and the OS Package Manager to determine whether an installed app can be updated. Every successive release must have a strictly higher versionCode. versionName is an arbitrary user-visible string (e.g., "3.2.1-beta") displayed on app stores and device Settings to indicate semantic product releases to human users.
Q5. What is the fundamental difference between Normal and Dangerous permissions?
Normal permissions (e.g., INTERNET, VIBRATE) pose minimal threat to user privacy and are granted automatically at installation time simply by declaring them in AndroidManifest.xml. Dangerous permissions (e.g., CAMERA, LOCATION, MICROPHONE) access private user data or sensitive hardware and require dynamic runtime authorization from the user via system dialogs while the application is active.
Q6. What constitutes a Build Variant in Gradle? Give an example.
A Build Variant is the Cartesian product of a Product Flavor and a Build Type (Build Variant = Flavor × Build Type). For example, if an app defines flavors free and paid, and build types debug and release, Gradle generates four distinct build variants: freeDebug, freeRelease, paidDebug, and paidRelease.
Q7. What is the difference between src/test and src/androidTest?
src/test/ houses Local Unit Tests that execute on the development workstation's desktop JVM. They do not have access to real Android OS framework services and run extremely quickly using mocks. src/androidTest/ houses Instrumented Tests that compile into a secondary test APK and execute on a physical Android handset or emulator, interacting directly with the real Android OS runtime, hardware, and view hierarchies (e.g., Espresso tests).
Q8. Why should strings always be defined in res/values/strings.xml instead of being hardcoded in code?
Hardcoding string literals inside Kotlin/Java source code or XML layouts prevents internationalization (i18n) and localization. Storing strings in strings.xml allows Android to automatically swap text based on device locale settings (e.g., loading res/values-es/strings.xml for Spanish users), eliminates duplicated strings across screens, and simplifies app translation and rebranding.