Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java
Original file line number Diff line number Diff line change
Expand Up @@ -6769,6 +6769,34 @@ public void sendMessage(String[] recipients, String subject, Message msg) {
public void dial(String phoneNumber) {
}

/// Indicates whether this platform can attempt to detect when an active phone
/// call is interrupting the app.
///
/// The default implementation returns `false`. Platforms with a best-effort
/// call interruption heuristic should override this method and `#isInCall()`.
///
/// #### Returns
///
/// `true` if call detection is implemented on this platform.
public boolean isCallDetectionSupported() {
return false;
}

/// Best-effort check for whether the platform believes a phone call is active.
///
/// This API is intentionally heuristic and should **not** be treated as a
/// reliable telephony state machine. Depending on platform restrictions it may
/// report false positives (e.g. other interruptions that temporarily move the
/// app out of the foreground) and false negatives (e.g. calls that are never
/// surfaced to the app lifecycle).
///
/// #### Returns
///
/// `true` if the platform currently believes a phone call interruption is active.
public boolean isInCall() {
return false;
}

/// Sends a SMS message to the given phone number
///
/// #### Parameters
Expand Down
26 changes: 26 additions & 0 deletions CodenameOne/src/com/codename1/ui/Display.java
Original file line number Diff line number Diff line change
Expand Up @@ -4466,6 +4466,32 @@ public void dial(String phoneNumber) {
impl.dial(phoneNumber);
}

/// Indicates whether this platform can attempt to detect active phone-call interruptions.
///
/// A `true` result means the platform provides a best-effort heuristic only.
/// It does **not** guarantee exact telephony state.
///
/// #### Returns
///
/// `true` if call detection is implemented on this platform.
public boolean isCallDetectionSupported() {
return impl.isCallDetectionSupported();
}

/// Best-effort check for whether the platform currently believes an active phone call
/// is interrupting the app.
///
/// This API is intentionally heuristic. It can produce false positives
/// (e.g. non-call interruptions like Control Center or app-switching) and false negatives.
/// Use it for UX hints and telemetry, not as a security or business-critical gate.
///
/// #### Returns
///
/// `true` if the platform currently believes a call interruption is active.
public boolean isInCall() {
return impl.isInCall();
}

/// Indicates the level of SMS support in the platform as one of:
/// `#SMS_NOT_SUPPORTED` (for desktop, tablet etc.),
/// `#SMS_SEAMLESS` (no UI interaction), `#SMS_INTERACTIVE` (with compose UI),
Expand Down
13 changes: 13 additions & 0 deletions Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ public class IOSImplementation extends CodenameOneImplementation {
private String userAgent;
private TextureCache textureCache = new TextureCache();
private static boolean dropEvents;
private static boolean callInterruptionActive;

private NativePathRenderer globalPathRenderer;
private NativePathStroker globalPathStroker;
Expand Down Expand Up @@ -6699,6 +6700,16 @@ public void dial(String phoneNumber) {
nativeInstance.dial("tel://" + phoneNumber);
}

@Override
public boolean isCallDetectionSupported() {
return true;
}

@Override
public boolean isInCall() {
return callInterruptionActive;
}

@Override
public boolean canDial() {
boolean s = super.canDial();
Expand Down Expand Up @@ -8497,6 +8508,7 @@ public boolean existsDB(String databaseName){
*/
public static void applicationWillResignActive() {
minimized = true;
callInterruptionActive = true;
if(instance.life != null) {
instance.life.applicationWillResignActive();
}
Expand Down Expand Up @@ -8667,6 +8679,7 @@ private void callOnActive(Runnable r) {
* here you can undo many of the changes made on entering the background.
*/
public static void applicationDidBecomeActive() {
callInterruptionActive = false;
ArrayList<Runnable> callbacks = null;
synchronized(instance.onActiveListeners) {
instance.isActive = true;
Expand Down
18 changes: 18 additions & 0 deletions docs/developer-guide/Miscellaneous-Features.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,24 @@ You can dial the phone by using:
Display.getInstance().dial("+999999999");
----

==== Call Detection

Codename One includes a generic call detection API via `Display.isCallDetectionSupported()` and `Display.isInCall()`.

This API is intentionally *best effort* and should only be used as a UX hint (for example, to defer a non-critical animation while the app is interrupted).

On iOS, `isInCall()` is inferred from app interruption lifecycle events, which means it can report `true` for non-call interruptions (e.g. Control Center, app switching, permission sheets), and some call flows may still be missed.

On Android, call detection is currently unsupported because robust detection would require invasive telephony permissions that are intentionally avoided.

[source,java]
----
Display display = Display.getInstance();
if (display.isCallDetectionSupported() && display.isInCall()) {
Log.p("Call interruption is currently active (heuristic)");
}
----

==== E-Mail

You can send an email via the platforms native email client with code such as this:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ public class TestCodenameOneImplementation extends CodenameOneImplementation {
private String currentAccessPoint;
private boolean vpnDetectionSupported;
private boolean vpnActive;
private boolean callDetectionSupported;
private boolean inCall;
private LocationManager locationManager;
private L10NManager localizationManager;
private ImageIO imageIO;
Expand Down Expand Up @@ -1063,6 +1065,8 @@ public void reset() {
currentAccessPoint = null;
vpnDetectionSupported = false;
vpnActive = false;
callDetectionSupported = false;
inCall = false;
startRemoteControlInvocations = 0;
stopRemoteControlInvocations = 0;
nativeTitle = false;
Expand Down Expand Up @@ -1156,6 +1160,11 @@ public void setVPNState(boolean detectionSupported, boolean active) {
this.vpnActive = active;
}

public void setCallState(boolean detectionSupported, boolean active) {
this.callDetectionSupported = detectionSupported;
this.inCall = active;
}

@Override
public LocationManager getLocationManager() {
return locationManager;
Expand Down Expand Up @@ -2743,6 +2752,16 @@ public boolean isVPNActive() {
return vpnActive;
}

@Override
public boolean isCallDetectionSupported() {
return callDetectionSupported;
}

@Override
public boolean isInCall() {
return inCall;
}

@Override
public boolean isAPSupported() {
return accessPointIds.length > 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ void testSetPropertyHandlesSpecialKeys() {
assertTrue(Component.isRevalidateOnStyleChange());
}

@Test
void testCallDetectionDelegatesToImplementation() {
Display display = Display.getInstance();

implementation.setCallState(true, true);
assertTrue(display.isCallDetectionSupported());
assertTrue(display.isInCall());

implementation.setCallState(false, false);
assertFalse(display.isCallDetectionSupported());
assertFalse(display.isInCall());
}

@Test
void testDebugRunnable() {
Display display = Display.getInstance();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.codenameone.examples.hellocodenameone.tests;

import com.codename1.ui.Display;

public class CallDetectionAPITest extends BaseTest {
@Override
public boolean runTest() {
try {
Display display = Display.getInstance();
display.isCallDetectionSupported();
display.isInCall();
done();
} catch (Throwable t) {
fail("Call detection API invocation failed: " + t);
}
return true;
}

@Override
public boolean shouldTakeScreenshot() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public final class Cn1ssDeviceRunner extends DeviceRunner {
new BytecodeTranslatorRegressionTest(),
new BackgroundThreadUiAccessTest(),
new VPNDetectionAPITest(),
new CallDetectionAPITest(),
new AccessibilityTest()));

public static void addTest(BaseTest test) {
Expand Down
Loading