Android: Notifications about changes in shared calendars

If you are sharing a calendar with another person or your family you probably want to know when events are added, deleted or modified. Because the calendar is shared you will see these new and modified events automatically in your calendar. However you will often see these events only a few days in advance because … Continue reading “Android: Notifications about changes in shared calendars”

If you are sharing a calendar with another person or your family you probably want to know when events are added, deleted or modified. Because the calendar is shared you will see these new and modified events automatically in your calendar. However you will often see these events only a few days in advance because you don’t watch the next weeks of your calendar all the time. And if an event has been deleted you won’t see it at all and will have to remember that there was an event.

An easy way to see which events have changed is the app “Calendar Watcher” . Just select the calendars you would like to watch and you will get status bar notifications about all changes in these calendars:

If an event was moved to another time, it will even show you the old and the new time of the event.

Syncing two calendars (e.g. Exchange into Google Calendar) on iOS and Android

Sometimes people are allowed to see their work calendar on their Android phone or iPhone. But they cannot share the calendar directly with their family to show them when they have work events. For this purpose it would be useful to mirror the work calendar (e.g. the Exchange calendar) into a Google calendar in their … Continue reading “Syncing two calendars (e.g. Exchange into Google Calendar) on iOS and Android”

Sometimes people are allowed to see their work calendar on their Android phone or iPhone. But they cannot share the calendar directly with their family to show them when they have work events. For this purpose it would be useful to mirror the work calendar (e.g. the Exchange calendar) into a Google calendar in their Google account so that they can share it with their family. By mirroring only the times of the events but not the contents (title, location, description) confidential data can be removed and only the important data (the time of the events) is shared.

If you have that problem and would like your family to see your work Exchange calendar, then just do the following:

  1. Create a new empty calendar e.g. “Work” in your Google Calendar account.
  2. Sync your Android phone/iPhone with your Exchange account and your Google Calendar account so that you can see calendars from both accounts.
  3. Install SyncCal for Android ( https://play.google.com/store/apps/details?id=com.calengoo.synccal ) or SyncCal for iOS ( https://itunes.apple.com/us/app/synccal/id796482010?mt=8 ) on your phone and use it to copy the Exchange calendar into your new “Work” calendar in your Google account.
  4. Check if you can see your work events in Google Calendar. If that works fine, you can share the calendar with your family to let them see your work events.

Apple AppStore Available on Google Play

Google’s new OAuth2 mechanism for native apps

So far the OAuth2 authentication could be performed with embedded browsers in iOS and Android. However Google has decided that they want apps to use the default browser instead now. One reason is that the default browser might already know the user due to cookies and can shorten the authentication process. The other reason might … Continue reading “Google’s new OAuth2 mechanism for native apps”

So far the OAuth2 authentication could be performed with embedded browsers in iOS and Android. However Google has decided that they want apps to use the default browser instead now. One reason is that the default browser might already know the user due to cookies and can shorten the authentication process. The other reason might be that apps could have theoretically captured the entered password when using an embedded browser. Anyway to switch an iOS app from the old authentication method using GTMOAuth2ViewControllerTouch to the new using OIDAuthorizationRequest requires only a few steps due to the useful library GTMAppAuth:

  1. Create a file called “Podfile” and use it to download and install GTMAppAuth:
    target 'YourProjectName' do
     platform :ios, '7.0'
     pod 'GTMAppAuth'
    end
  2. Install Cocoapods if necessary and then run
    pod install
  3. Open the created Xcode workspace file. Delete the following old files if you have them:
    GTMGatherInputStream.h
    GTMGatherInputStream.m
    GTMMIMEDocument.h
    GTMMIMEDocument.m
    GTMReadMonitorInputStream.h
    GTMReadMonitorInputStream.m
  4. Add a property to your AppDelegate.h file:
    @property(nonatomic, retain) id currentAuthorizationFlow;
  5. Add a handler to your AppDelegate.m file in the application:openURL:sourceApplication:annotation: method. Put this right at the start of the method:
    if ([_currentAuthorizationFlow resumeAuthorizationFlowWithURL:url]) {
     _currentAuthorizationFlow = nil;
     return YES;
    }
  6. Now you are ready to replace your old authentication code which used GTMOAuth2ViewControllerTouch with the new code (taken partially from the GTMAppAuth website):
    OIDServiceConfiguration *configuration = [GTMAppAuthFetcherAuthorization configurationForGoogle];
    
    OIDAuthorizationRequest *request = [[[OIDAuthorizationRequest alloc] initWithConfiguration:configuration
     clientId:GOOGLE_CLIENT_ID
     clientSecret:GOOGLE_CLIENT_SECRET
     scopes:@[OIDScopeEmail, @"https://www.googleapis.com/auth/calendar"]
     redirectURL:[NSURL URLWithString:@"com.example.yourapp:/oauthredirect"]
     responseType:OIDResponseTypeCode
     additionalParameters:nil] autorelease];
    
     // performs authentication request
     AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
     appDelegate.currentAuthorizationFlow =
     [OIDAuthState authStateByPresentingAuthorizationRequest:request
     presentingViewController:self
     callback:^(OIDAuthState *_Nullable authState,
     NSError *_Nullable error) {
     if (authState) {
     // Creates the GTMAppAuthFetcherAuthorization from the OIDAuthState.
     GTMAppAuthFetcherAuthorization *authorization = [[[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState] autorelease];
  7. Using the authorization result you can now call your existing handler:
    [self viewControllerFinishedWithAuth:authorization error:error accessToken:authState.lastTokenResponse.accessToken];
     NSLog(@"Got authorization tokens. Access token: %@",
     authState.lastTokenResponse.accessToken);
     } else {
     NSLog(@"Authorization error: %@", [error localizedDescription]);
     }
     }];
    
    }
    
    - (void)viewControllerFinishedWithAuth:(id )auth error:(NSError *)error accessToken:(NSString *)accessToken {
     if (error == nil) {
     // OAuth2 Login was successful
     // Serialize to Keychain
     [GTMOAuth2KeychainCompatibility saveAuthToKeychainForName:keychainNameOAuth2
     authentication:auth];

    Now you can use “accessToken” to access Google services. And due to GTMOAuth2KeychainCompatibility you can keep your existing functions that load the authentication data from the keychain.

  8. In the Info.plist file of your app you have to configure the “com.example.yourapp” URL mentioned above so that the browser can open your app and send you the result:
    CFBundleURLTypes
    
        
            CFBundleTypeRole
            Editor
            CFBundleURLSchemes
            
                com.example.yourapp
            
        
    
    

So that’s all you have to change for an existing iOS app. You can find further information in Google’s blogpost.

UI Test under Android (UIAutomator/Espresso)

Android also offers a way to run UI tests. Several libraries are available but the latest officially supported libraries seem to be Espresso and UIAutomator. UIAutomator is newer but requires API Level 14. You can find a short description here: http://developer.android.com/training/testing/ui-testing/uiautomator-testing.html Espresso needs only API level 8 but is more limited and cannot create screenshots … Continue reading “UI Test under Android (UIAutomator/Espresso)”

Android also offers a way to run UI tests. Several libraries are available but the latest officially supported libraries seem to be Espresso and UIAutomator. UIAutomator is newer but requires API Level 14. You can find a short description here:

http://developer.android.com/training/testing/ui-testing/uiautomator-testing.html

Espresso needs only API level 8 but is more limited and cannot create screenshots on its own:

http://developer.android.com/training/testing/ui-testing/espresso-testing.html

While they are similar to the iOS UI tests they seem to be much more hardware dependent. E.g. where iOS can also find and tap table rows that are outside the screen (which makes it easier to support different screen sizes for the tests) Android “sees” only what is on the screen, making it necessary to add swipe gestures, which makes the tests dependent on a certain screen size.

To find out how to access a certain screen element you can use the “uiautomatorviewer” program.

Automatically starting the app on multiple devices

If your app is compatible with older Android versions but you are also using new features, you have to take care not to load the classes that are only available on newer Android versions on old Android versions, otherwise your app will crash. So it is also useful to run the tests on multiple emulators with different Android versions. For this purpose you can add some lines to your build.gradle file:

task testemulator << {
  emulatorstart("Test16")
  emulatorstart("Test23")
  emulatorstart("Test40")
}

def emulatorstart(emulatorname) {
  Process process = ("emulator -avd " + emulatorname + " -gpu off").execute()
  Thread.sleep(60000)
  "adb -e shell input keyevent 82".execute().waitFor() // unlock screen
  "adb -e uninstall com.example.app".execute().waitFor()
  "adb -e install build/outputs/apk/example.apk".execute().waitFor()
  "adb -e shell am start -n com.example.app/com.example.app.Main".execute().waitFor()
  Thread.sleep(10000)

  // Here you could run your tests. In this example a screenshot is saved.
  "adb shell screencap -p /sdcard/screen.png".execute().waitFor()
  ("adb pull /sdcard/screen.png " + emulatorname + ".png").execute().waitFor()
  "adb shell rm /sdcard/screen.png".execute().waitFor()
  process.waitForOrKill(10000)
}

By running

gradle testemulator

your app will be installed on all specified emulators (e.g. "Test16", "Test23" and "Test40") and started. Afterward a screenshot with the name of the emulator will be created. This way you can run it (which takes a while) and then check the screenshots to see if it was successfully started on all these emulators and Android versions.

Screenshots

Screenshots on newer Android devices can be created in the way described above. However e.g. Android 2.x does not have the "screencap" program installed. If you would like to create screenshots with older Android versions or if "screencap" does not work, you can use a Java program that you can find here. To integrate it into the gradle script above just replace the three lines

"adb shell screencap -p /sdcard/screen.png".execute().waitFor()
("adb pull /sdcard/screen.png " + emulatorname + ".png").execute().waitFor()
"adb shell rm /sdcard/screen.png".execute().waitFor()

with

screenshot(emulatorname);

and add the following function to your gradle file:

def screenshot(emulatorname) {
    exec {
        executable "java"
        args "-jar", "/path/to/your/downloaded/screenshot.jar", "-e", "screenshots/" + emulatorname + ".png"
    }
}

Opening all screenshots

To open all screenshots under MacOS to review them, just use the command

open screenshots/*

This will start Preview and open all images in a single window with a list of thumbnails so that you can quickly see if your app was started correctly on all these devices.

Selecting ListView elements

When displaying lists it can often happen that you have to click on an entry that is not visible on the screen. When it isn't visible on the screen then there is not even a view for it, so the normal test functions cannot find and click that view. That's what "onData" is for. Instead of searching for a visible view it searches the adapters of the visible views for matching elements. E.g.

onData(hasToString("Test")).perform(click());

clicks on the row of any ListView (or Spinner or other element with an adapter) that returns "Test" from its "toString()" method. If you have multiple objects with an adapter on the screen (e.g. a ListView and Spinners), you can limit the commmand to the ListView

onData(hasToString("Test")).inAdapterView(withClassName(Matchers.is(ListView.class.getName()))).perform(click());