Skip to content

Commit

Permalink
fix(android): Ti.App wrongly fires pause/resume events when opening/c…
Browse files Browse the repository at this point in the history
…losing child windows (#10658)

- Fixed bug where Ti.App wrongly fires pause/resume events when opening/closing child windows
- Updated Ti.App docs to indicate Android supported these events since 7.5.0
- Modified to only fire a "resume" event after a "pause" like iOS
- Changed "TiApplicationLifecycle" class' static variables to instance member variables. They don't need to be static.

Fixes TIMOB-26746
  • Loading branch information
jquick-axway authored and sgtcoolguy committed Mar 25, 2019
1 parent 53524fc commit bf6530e
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,82 @@
import com.appcelerator.aps.APSAnalytics;

import org.appcelerator.kroll.common.Log;
import org.appcelerator.kroll.KrollModule;

public class TiApplicationLifecycle implements Application.ActivityLifecycleCallbacks
{
private static final String TAG = "TiApplicationLifecycle";

private TiApplication tiApp = TiApplication.getInstance();
private static int activityCount = 0;
private int existingActivityCount;
private int visibleActivityCount;
private boolean wasPaused;

@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState)
{
// Reset "wasPaused" state when creating the 1st activity in a UI task.
// Needed to detect if the app is resuming after being paused.
if (this.existingActivityCount <= 0) {
this.wasPaused = false;
}

// Increment count of all known activities.
this.existingActivityCount++;
}

@Override
public void onActivityStarted(Activity activity)
{
if (activityCount == 0 && tiApp != null && tiApp.isAnalyticsEnabled()) {
APSAnalytics.getInstance().sendAppForegroundEvent();
// If no activities have been started, then app is going to be put into the foreground.
if (this.visibleActivityCount == 0) {
// Fire Ti.App resume events.
// Note: The "resume" event should only be fired after a "pause" event and never on app startup.
KrollModule appModule = this.tiApp.getModuleByName("App");
if (appModule != null) {
if (this.wasPaused) {
appModule.fireEvent(TiC.EVENT_RESUME, null);
}
appModule.fireEvent(TiC.EVENT_RESUMED, null);
}

// Post analytics for this event, if enabled.
if (this.tiApp.isAnalyticsEnabled()) {
APSAnalytics.getInstance().sendAppForegroundEvent();
}
}
activityCount++;

// Increment number of "started" activities. These are activities that are currently in the foreground.
// Note: Should never be more than 1, unless some of these activities are fragments.
this.visibleActivityCount++;
}

@Override
public void onActivityStopped(Activity activity)
{
if (activityCount == 1 && tiApp != null && tiApp.isAnalyticsEnabled()) {
APSAnalytics.getInstance().sendAppBackgroundEvent();
// If this is the last activity being stopped, then the app is going to be put into the background.
if (this.visibleActivityCount == 1) {
// Flag that we've been paused at least once for this UI task.
this.wasPaused = true;

// Fire Ti.App pause events.
KrollModule appModule = this.tiApp.getModuleByName("App");
if (appModule != null) {
appModule.fireEvent(TiC.EVENT_PAUSE, null);
appModule.fireEvent(TiC.EVENT_PAUSED, null);
}

// Post analytics for this event, if enabled.
if (this.tiApp.isAnalyticsEnabled()) {
APSAnalytics.getInstance().sendAppBackgroundEvent();
}
}

// Decrement count of started/visible activities.
this.visibleActivityCount--;
if (this.visibleActivityCount < 0) {
this.visibleActivityCount = 0;
}
activityCount--;
}

@Override
Expand All @@ -62,5 +109,10 @@ public void onActivitySaveInstanceState(Activity activity, Bundle outState)
@Override
public void onActivityDestroyed(Activity activity)
{
// Decrement total activity count.
this.existingActivityCount--;
if (this.existingActivityCount < 0) {
this.existingActivityCount = 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1375,11 +1375,6 @@ protected void onPause()
if (activityProxy != null) {
activityProxy.fireEvent(TiC.EVENT_PAUSE, null);
}
KrollModule appModule = tiApp.getModuleByName("App");
if (appModule != null) {
appModule.fireEvent(TiC.EVENT_PAUSE, null);
appModule.fireEvent(TiC.EVENT_PAUSED, null);
}

synchronized (lifecycleListeners.synchronizedList())
{
Expand Down Expand Up @@ -1421,11 +1416,6 @@ protected void onResume()
if (activityProxy != null) {
activityProxy.fireEvent(TiC.EVENT_RESUME, null);
}
KrollModule appModule = tiApp.getModuleByName("App");
if (appModule != null) {
appModule.fireEvent(TiC.EVENT_RESUME, null);
appModule.fireEvent(TiC.EVENT_RESUMED, null);
}

synchronized (lifecycleListeners.synchronizedList())
{
Expand Down
13 changes: 8 additions & 5 deletions apidoc/Titanium/App/App.yml
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,8 @@ events:
Note that calls to functions that modify the UI during this event may be partially executed,
**up to** the UI call before the suspension. See [paused](Titanium.App.paused) event. If this happens, the remainder of the code will
be executed after the application is resumed, but before the `resume` event is triggered.
platforms: [iphone, ipad]
platforms: [android, iphone, ipad]
since: {android: "7.5.0", iphone: "0.8", ipad: "0.8"}

- name: paused
summary: Fired when the application transitions to the background on a multitasked system.
Expand All @@ -214,8 +215,8 @@ events:
[Monitoring Application State Changes](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622997-applicationdidenterbackground?language=objc)
for the exact behavior that triggers this event.
platforms: [iphone, ipad]
since: '2.1.0'
platforms: [android, iphone, ipad]
since: {android: "7.5.0", iphone: "2.1.0", ipad: "2.1.0"}

- name: proximity
summary: Fired when the proximity sensor changes state.
Expand Down Expand Up @@ -280,7 +281,8 @@ events:
See the `applicationWillEnterForeground` section of the official Apple documentation about
[Monitoring Application State Changes](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623076-applicationwillenterforeground)
for the exact behavior that triggers this event.
platforms: [iphone, ipad]
platforms: [android, iphone, ipad]
since: {android: "7.5.0", iphone: "0.8", ipad: "0.8"}

- name: resumed
summary: Fired when the application returns to the foreground.
Expand All @@ -296,7 +298,8 @@ events:
Note: This event will not be fired for URL's in iOS 10+ that are handled by the <Modules.SafariDialog>
or <Titanium.UI.WebView>, because Apple does not call the `applicationDidBecomeActive` for
URL-handling anymore. Instead, use the `handleurl` event in <Titanium.App.iOS>.
platforms: [iphone, ipad]
platforms: [android, iphone, ipad]
since: {android: "7.5.0", iphone: "0.8", ipad: "0.8"}

- name: started
summary: Fired after the "app.js" or "alloy.js" gets executed during application startup.
Expand Down
54 changes: 54 additions & 0 deletions tests/Resources/ti.app.addontest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Appcelerator Titanium Mobile
* Copyright (c) 2011-Present by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
/* eslint-env mocha */
/* global Ti */
/* eslint no-unused-expressions: "off" */
'use strict';
var should = require('./utilities/assertions');

describe('Titanium.App', function () {
it.android('pause/resume events', function (finish) {
this.timeout(5000);
let wasPauseEventReceived = false;
let wasResumeEventReceived = false;

// Handle Ti.App pause/resume events. They happen when app is sent to background/foreground.
// - "pause" event must be received before "paused" event.
// - "resume" event must be received before "resumed" event.
Ti.App.addEventListener('pause', function pauseEventHandler(e) {
Ti.API.info('Received event: ' + e.type);
Ti.App.removeEventListener(e.type, pauseEventHandler);
wasPauseEventReceived = true;
});
Ti.App.addEventListener('paused', function pausedEventHandler(e) {
Ti.API.info('Received event: ' + e.type);
Ti.App.removeEventListener(e.type, pausedEventHandler);
should(wasPauseEventReceived).be.true;
Ti.Android.currentActivity.startActivity(Ti.App.Android.launchIntent); // Resume this app.
});
Ti.App.addEventListener('resume', function resumeEventHandler(e) {
Ti.API.info('Received event: ' + e.type);
Ti.App.removeEventListener(e.type, resumeEventHandler);
wasResumeEventReceived = true;
});
Ti.App.addEventListener('resumed', function resumedEventHandler(e) {
Ti.API.info('Received event: ' + e.type);
Ti.App.removeEventListener(e.type, resumedEventHandler);
should(wasResumeEventReceived).be.true;
finish();
});

// Navigate to the device's home screen. Equivalent to pressing the "home" button.
// This should fire this app's "pause" and "paused" events.
const homeIntent = Ti.Android.createIntent({
action: Ti.Android.ACTION_MAIN,
});
homeIntent.addCategory(Ti.Android.CATEGORY_HOME);
homeIntent.setFlags(Ti.Android.FLAG_ACTIVITY_NEW_TASK);
Ti.Android.currentActivity.startActivity(homeIntent);
});
});

0 comments on commit bf6530e

Please sign in to comment.