---
title: 'The Development Environment'
source: 'https://academia.sh/en/courses/android-platform-and-tools/development-environment'
course: 'Android Platform and Tools'
language: en
updated: '2026-10-21T05:02:36+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Distinguishes the fulfillment that an emulator and a physical device give to the same install request'
    - 'Measures that a device API level below the minimum API level is unmet and that a debugging authorization waiting for approval is pending'
    - 'Distinguishes the places of the log tool, the debugger and the profiler in the development environment'
    - 'Verifies with the bounding measurement that a processor architecture mismatch of native code is unmet'
---

# The Development Environment

A model of the request to install an app on a device, measured against the API level, the debugging authorization and the processor architecture.

The previous lesson measured which build variants come out of the same source. When a variant is built, a place is needed to run it. The development environment is the set of tools in which source code is written, built, installed on a device and monitored. This lesson measures what a device gives to an install request.

The measurement axis of the course does not change: how does the platform fulfill this request. There are four fulfillments: full, substitute, pending and unmet. The rule, not a label, gives the fulfillment: a given value that equals the wanted one is full, and one that differs is substitute; pending and unmet come directly from the outcome. The file below carries that rule and is repeated verbatim in every lesson. The count of 3/2/2/1 from the opening lesson is not changed here.

```kotlin
// Fulfillment.kt — classifies how the platform fulfills a request
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "pending"
        is Outcome.Refused -> "unmet"
        is Outcome.Given -> if (outcome.value == wanted) "full" else "substitute"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "given=${outcome.value}"
            is Outcome.Waiting -> "waiting-on=${outcome.on}"
            is Outcome.Refused -> "reason=${outcome.reason}"
        }
        println("$label: wanted=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("full", "substitute", "pending", "unmet")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

## Devices and Tools

An app is run in two places. An emulator is a virtual device that imitates a device on the computer. A physical device is real hardware and is connected to the computer. Both offer the same interface to the tools. The tools do four jobs:

- The device connection tool finds the device, installs the app and builds a bridge between the device and the computer.
- The log tool shows the log lines the device writes.
- The debugger stops the running app and inspects its variables.
- The profiler measures processor, memory and network use over time.

The fragment below shows the build setting that declares the minimum API level of the app. It is a script that the platform tools read and is not run.

```kotlin
// module build script
android {
    defaultConfig {
        minSdk = 26
    }
}
```

This setting limits which devices the app can be installed on. If the device's API level is below this value, the install is not performed. The model builds this rule of the platform.

## The Install Request

The model describes a device by its name, its API level, its processor architecture and whether the debugging authorization has been approved. Three cases are separated in the install request:

- If the device's API level is below the minimum API level, the install is not given: unmet, reason `api-level`.
- If the API level is sufficient but the device has not yet approved the debugging authorization, the install waits: pending, waiting on `user-approval`.
- If both conditions are met, the wanted app is installed as it is: full.

```kotlin
// Install.kt — a model of the install request
class Device(val name: String, val apiLevel: Int, val abi: String, val debugApproved: Boolean)

fun resolveInstall(wanted: String, minApi: Int, device: Device): Ask {
    val outcome = when {
        device.apiLevel < minApi -> Outcome.Refused("api-level")
        !device.debugApproved -> Outcome.Waiting("user-approval")
        else -> Outcome.Given(wanted)
    }
    return Ask(device.name, wanted, outcome)
}
```

In the measurement the minimum API level is 26. There are four devices: a current emulator, a physical device that has given approval, a physical device that has not yet passed the approval screen, and an old device.

```kotlin
// Main.kt — the install request
fun main() {
    val devices = listOf(
        Device("emulator", 34, "arm64", true),
        Device("phone-approved", 30, "arm64", true),
        Device("phone-unapproved", 30, "arm64", false),
        Device("old-phone", 23, "arm64", true),
    )
    val asks = devices.map { resolveInstall("app", 26, it) }
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
emulator: wanted=app | given=app -> full
phone-approved: wanted=app | given=app -> full
phone-unapproved: wanted=app | waiting-on=user-approval -> pending
old-phone: wanted=app | reason=api-level -> unmet
full: 2
substitute: 0
pending: 1
unmet: 1
```

Two devices got full, one got pending and one got unmet. The unapproved phone was not refused; the phone whose API level was not enough was refused.

**PS1.** The model builds the debugging authorization as a single logical value. On a real device this approval is given through user interaction and is kept separately for each device; the measurement does not imitate this.

**PS2.** The install model makes no difference between an emulator and a physical device; the difference is only in the sensor measurement. In reality there are also differences in performance and network.

## The Arrival of Approval

A pending request becomes full when approval arrives. To measure this, the same device is asked twice: first unapproved, then approved.

```kotlin
// Main.kt — approval before and after
fun main() {
    val before = Device("phone", 30, "arm64", false)
    val after = Device("phone", 30, "arm64", true)
    val asks = listOf(
        resolveInstall("app", 26, before),
        resolveInstall("app", 26, after),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
phone: wanted=app | waiting-on=user-approval -> pending
phone: wanted=app | given=app -> full
full: 1
substitute: 0
pending: 1
unmet: 0
```

The same request was fulfilled differently while pending and after approval. A pending result names a user action; when the action arrives, the same request becomes full.

## Debugging and Profiling Tools

Three tools attach to the same running app but do different jobs. The log tool streams the lines the app and the system write; it does not stop the app. The debugger stops the app at a point set in the source code, reads the variables and steps forward one step at a time. The profiler records processor, memory and network use as a timeline without stopping the app. Because the debugger interferes with how the app runs, the build type must allow it. The script fragment below declares that a distribution build is closed to debugging.

```kotlin
// module build script
android {
    buildTypes {
        getByName("release") { isDebuggable = false }
        getByName("debug") { isDebuggable = true }
    }
}
```

The model tests the three tools according to this setting. The log tool is given full in every build. The debugger is given full only in a build that is open to debugging; if it is closed, it is unmet, reason `not-debuggable`. The profiler gives a restricted measurement in a closed build; the model counts this as substitute.

```kotlin
// Tools.kt — a model of attaching tools
fun attachTool(tool: String, debuggable: Boolean): Ask {
    val outcome = when (tool) {
        "log-tool" -> Outcome.Given(tool)
        "debugger" ->
            if (debuggable) Outcome.Given(tool) else Outcome.Refused("not-debuggable")
        "profiler" ->
            if (debuggable) Outcome.Given(tool) else Outcome.Given("restricted-profiler")
        else -> Outcome.Refused("no-tool")
    }
    return Ask("$tool/${if (debuggable) "debug" else "release"}", tool, outcome)
}
```

In the measurement each of the three tools is asked in two builds.

```kotlin
// Main.kt — attaching tools
fun main() {
    val asks = listOf(
        attachTool("log-tool", true),
        attachTool("debugger", true),
        attachTool("profiler", true),
        attachTool("log-tool", false),
        attachTool("debugger", false),
        attachTool("profiler", false),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
log-tool/debug: wanted=log-tool | given=log-tool -> full
debugger/debug: wanted=debugger | given=debugger -> full
profiler/debug: wanted=profiler | given=profiler -> full
log-tool/release: wanted=log-tool | given=log-tool -> full
debugger/release: wanted=debugger | reason=not-debuggable -> unmet
profiler/release: wanted=profiler | given=restricted-profiler -> substitute
full: 4
substitute: 1
pending: 0
unmet: 1
```

The same three tools were fulfilled differently in the two builds: the difference came from the build type, not from the tool. The debugger was not given at all in the distribution build, while the profiler was given as something else.

**PS3.** The model builds the behavior of the profiler in a closed build as a restricted measurement. Which measurements remain in the real platform depends on the tool and the version; the measurement does not verify this.

## Emulator and Physical Device

An emulator imitates sensor data: it gives virtual values for location, acceleration and the camera. On a physical device these values come from real hardware. If the device is not connected, nothing can be given, but once the connection is made the request can be asked again. The model separates three cases: a device that is not connected is pending, a connected emulator is substitute, and a connected physical device is full.

```kotlin
// Sensor.kt — a model of the sensor request
class Target(val name: String, val connected: Boolean, val emulated: Boolean)

fun resolveSensor(wanted: String, target: Target): Ask {
    val outcome = when {
        !target.connected -> Outcome.Waiting("device-connection")
        target.emulated -> Outcome.Given("virtual-$wanted")
        else -> Outcome.Given(wanted)
    }
    return Ask(target.name, wanted, outcome)
}
```

In the measurement there are three targets: a phone with its cable plugged in, a running emulator, and a phone whose cable has been unplugged.

```kotlin
// Main.kt — the sensor request
fun main() {
    val targets = listOf(
        Target("phone", true, false),
        Target("emulator", true, true),
        Target("phone-unplugged", false, false),
    )
    val asks = targets.map { resolveSensor("location", it) }
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
phone: wanted=location | given=location -> full
emulator: wanted=location | given=virtual-location -> substitute
phone-unplugged: wanted=location | waiting-on=device-connection -> pending
full: 1
substitute: 1
pending: 1
unmet: 0
```

The value the emulator gives is a virtual version of the wanted value; that is why it is a substitute. The phone with a broken connection was not refused; it is waiting.

**PS4.** The model separates the virtual value by its name. How the virtual location is given in a real emulator and which of the sensor types are imitated were not measured.

## The Bounding Measurement

Native code is built for a specific processor architecture. A device whose API level is sufficient still cannot run the native part of the app if the architecture does not match. The model says by name which field it compares: first the API level, then the processor architecture.

```kotlin
// Abi.kt — the architecture comparison
fun resolveNative(wanted: String, minApi: Int, builtFor: Set<String>, device: Device): Ask {
    val outcome = when {
        device.apiLevel < minApi -> Outcome.Refused("api-level")
        device.abi !in builtFor -> Outcome.Refused("processor-architecture")
        else -> Outcome.Given(wanted)
    }
    return Ask(device.name, wanted, outcome)
}
```

The native code is built only for `arm64`. Although its API level is sufficient, the emulator is on the `x86` architecture; a second emulator is on the `arm64` architecture.

```kotlin
// Main.kt — architecture mismatch
fun main() {
    val builtFor = setOf("arm64")
    val devices = listOf(
        Device("emulator-x86", 34, "x86", true),
        Device("emulator-arm", 34, "arm64", true),
        Device("old-emulator", 23, "x86", true),
    )
    val asks = devices.map { resolveNative("native-code", 26, builtFor, it) }
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
emulator-x86: wanted=native-code | reason=processor-architecture -> unmet
emulator-arm: wanted=native-code | given=native-code -> full
old-emulator: wanted=native-code | reason=api-level -> unmet
full: 1
substitute: 0
pending: 0
unmet: 2
```

The first emulator differs from the second, which has the same API level, only in architecture, and the result changes. On the third device neither condition is met; the model makes the comparison in order and gives the name of the first field that does not match, which is why the reason is `api-level`.

**PS5.** The model accepts only an exact match. On a real platform one architecture can run another through a translation layer; this was not measured.

## Summary

- This lesson's criterion: **fulfillment**; whether what is installed is the wanted one, not where the device runs. This lesson's fulfillment: **pending**.
- The development environment is the whole of the tools for writing, building, installing and monitoring; an emulator and a physical device use the same install interface.
- If the device's API level is below the minimum API level, the install is unmet.
- If the debugging authorization has not been approved, the install is pending; when approval arrives, it becomes full.
- If the processor architecture of native code does not match the device's, the result is unmet even if the API level is sufficient.

## Next Step

Once an app is installed on a device, it has to ask the user for access to sensitive data and hardware. The next lesson covers the permission model.
