A Daunting Task – Tiffany goes to Cupertino

A Daunting Task – Tiffany goes to Cupertino

TiffanyScreens in the Mac App Store

I never really liked going to “PowerPoint Meetings”, sharing the screen content with others during a meeting, usually requires to connect a projector to the presenter’s laptop. In a lengthy process, the laptop’s screen resolution and refresh-rate needed to be manually adjusted to synchronize with the projector. What follows is a lecture style presentation, featuring slides being projected, hugely magnified onto one of the meeting room’s walls; often, lights need to be dimmed, and listeners either doze off or start checking email.

A couple years back, I wrote TiffanyScreens, which allows you to share presentations (or any screen content), without requiring a projector.
Imagine a scenario, where every participant brought a laptop computer to a meeting and watched the presentation on that laptop’s display – participants sit on a table facing each other, instead of the wall. No adjustments are necessary; images are scaled automatically on arrival, to best match the receiver’s display-capability. To support lively meetings, everyone participating, can with a single button click, turn his computer into the presenting device.



tsp5

During the development, I had small engineering groups in mind, but it became more and more obvious that instructors, educators, and educational institutions would appreciate TiffanyScreens the most. Listening to users’ feedback, TiffanyScreens has been iteratively improved, refined, and optimized.

For instance, to accommodate instructors and teachers, only one peer needs to have a licensed copy of the application installed and since you never know what kind of laptop users bring, TiffanyScreens is of course a multi-platform application, running on Windows, Linux, and the Mac. In fact, most of TiffanyScreens is implemented in Java.

But almost like the boiling frog story, Apple slowly turned up the heat on Java and Java developers. OS X 10.7 Lion introduced the Mac Application Store, in which Java applications were not welcome. OS X 10.8 Mountain Lion makes it extra hard for users to install applications that are not distributed through the Mac App Store.


Mac Security Settings


With Apple withdrawing most of its Java support, it’s now Oracle, providing up to date Java runtime and Java SDK of Mac OS X. However, recent Zero-Day Java security vulnerabilities make it less like that users’ machines have a Java runtime installed.

Considering all these obstacles, I could have let TiffanyScreens run its course, letting it die a slow death, but during a couple of days off work, over the Christmas break, I came up with a decisive rescue plan … putting TiffanyScreens into the Mac Application Store.

Putting TiffanyScreens into the Mac Application Store

1. Administrative Tasks

Trying to put a Java desktop app into Apple’s Mac App Store is a daunting task, no mean feat, some may even call it a total waste of time. However, TiffanyScreens, the innovative presentation and screen sharing tool, used and loved by many educational institutions worldwide, must not die …

1.1. Joining the Mac Developer Program

It all started with a $99 investment, purchasing a Mac Developer ID and getting into the Mac Developer Program.

https://developer.apple.com/programs/start/standard/

1.2. Request a OS X Signing Certificate

The next step was all about preparing my machine to sign and submit a binary to the Mac App Store approval process.
https://developer.apple.com/certificates/index.action#maccertrequest


Screen Shot 2013-01-20 at 3.31.22 PM

Since TiffanyScreens does neither need the iCloud nor Apple Push Notifications, I did forgo the Mac App Development Certificate but requested and installed:

  • Mac App Certificate
  • Mac Installer Certificate
  • Developer ID Application Certificate
  • Developer ID Installer Certificate



Screen Shot 2013-01-20 at 3.37.49 PM

1.3. Creating an Application Bundle ID

I created and Application Bundle ID here: https://developer.apple.com/certificates/index.action#bundle
An App ID is an identifier used by iOS and Mac OS X to recognize your app. This was pretty simple and straight forward. I just needed to enter my applications name and a bundle identifier.

  • TiffanyScreens Pro
  • com.carlsbadcubes.tiffany.TiffanyScreens.pro

1.4. Creating an Application on the iTunes Store

I opened https://itunesconnect.apple.com/ and in “Manage Your Applications”, selected “Add New App” followed by “Mac OS X App”
Here, the application got defined in more detail, like it will appear in the iTunes Store. Completing this step also included deciding on a primary and secondary application category, as well as uploading a couple of screen shots. I think it’s important to note that once an application has been approved, those screen shots cannot easily be changed.

This pretty much concluded the administrative tasks and now the much more interesting engineering tasks began.


Screen Shot 2013-01-20 at 3.49.08 PM

2. Engineering Tasks

2.1. Java Runtime

Since I wanted to bundle TiffanyScreens with the latest Java Runtime, I downloaded and deployed JDK 7 on my development and build machine.
http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html
Installing jdk-7u11-macosx-x64.dmg, the most recent Java release for 64-bit Intel Macs, meant getting something like this when typing java -version into a terminal.

java version "1.7.0_11"
Java(TM) SE Runtime Environment (build 1.7.0_11-b21)
Java HotSpot(TM) 64-Bit Server VM (build 23.6-b04, mixed mode)



In ~/.bash_profile I have JAVA_HOME declared like this:
export set JAVA_HOME=`/usr/libexec/java_home`
which in fact points here:
/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java_home
which is an executable and returns:
/Library/Java/JavaVirtualMachines/jdk1.7.0_11.jdk/Contents/Home
and this location contains the Java Runtime (jre) folder, which will eventually be bundled with TiffanyScreens.

2.2. A Single JAR

TiffanyScreens was already packaged to be distributed as a Mac friendly DMG. What I mean with that is that TiffanyScreens was always packaged into a single Jar. 3rd party libraries like Swixml for instance get unpacked
<unzip src="${swixml.jar}" dest="${build.dest}"/>
during the build process and re-packaged together with TiffanyScreens’ classes and resources into a single jar file.

The package target for instance in my TiffanyScreeens’ Ant build.xml looks something like this:

<target name="package" depends="compile,unjarparties" description="Creates the class package">

       <!-- Filters defined specifically for updating the manifest -->
       <filter token="version.spec" value="${version.spec}"/>
       <filter token="version.impl" value="${version.impl}"/>
       <copy
           todir="${build.dest}/META-INF"
           filtering="yes">
           <fileset dir="${package.dir}/META-INF"/>
       </copy>
       <copy
           todir="${build.dest}" filtering="no">
           <fileset dir="${build.src}" includes="**/*.xml"/>
       </copy>
       <copy
           todir="${build.dest}" filtering="no">
           <fileset dir="${build.src}" includes="img/*"/>
       </copy>
       <jar jarfile="${build.dir}/${name}.jar"
            basedir="${build.dest}"
            excludes="META-INF/MANIFEST.MF"
            manifest="${build.dest}/META-INF/MANIFEST.MF"
           />
</target>		

2.3. AppBundler Ant Target

Oracle also has a document on its site that explains some of the things I wanted to do, Bundling the JRE with Tiffany. Unfortunately, the example they show is somewhat oversimplified to be really helpful. However, it has links to the Java Application Bundler project, which contains a Jar, providing an Ant target, to bundle the JRE with a Java app.

So I download the appbundler-1.0.jar and read the related documentation.

Going back and force between the documentation and my Ant build file, I ended up with an target configured like this:

    <taskdef name="bundleapp" classpath="${appbundler.jar}" classname="com.oracle.appbundler.AppBundlerTask"/>
    <target name="appbundle" depends="package" description="Create Mac OSX Application Bundle">
        <bundleapp
            outputdirectory="/Users/wolf/Work/Tiffany/build/dist"
            name="${Name}"
            displayname="${Name}"
            identifier="${mainclass}.${bid}"
            icon="res/${bid}.icns"
            shortversion="${version}"
            signature="CABA"
            copyright="2006-2013 Techcasita Productions."
            applicationCategory="public.app-category.productivity"
            mainclassname="${mainclass}"
            >
            
            <!-- JDK / JRE -->
            <runtime dir="/Library/Java/JavaVirtualMachines/jdk1.7.0_11.jdk/Contents/Home"/>

            <!-- The bundleapp task doesn't support classpathref so all
                the run classpath entries must be stated here too.
            -->
            <classpath file="./build/${name}.jar"/>
            <classpath file="./libs/libscreen.jnilib"/>

            <option value="-Xdock:name=${Name}"/>
            <option value="-Xdock:icon=Contents/Resources/${bid}.icns"/>
            <option value="-Dapple.laf.useScreenMenuBar=true"/>
            <option value="-Dcom.apple.macos.use-file-dialog-packages=true"/>
            <option value="-Dcom.apple.macos.useScreenMenuBar=true"/>
            <option value="-Dcom.apple.mrj.application.apple.menu.about.name=${Name}"/>
            <option value="-Dcom.apple.smallTabs=true"/>
            <option value="-Dcom.apple.textantialiasing=true"/>
            <option value="-Dcom.apple.antialiasing=true"/>
            <option value="-Dcom.apple.showGrowBox=false"/>
            <option value="-Dapple.awt.fullscreencapturealldisplays=false"/>
            <option value="-Dapple.awt.fullscreenusefade=true"/>
            <option value="-Djava.library.path=$APP_ROOT/Contents/Java"/>
            <option value="-Xms32M"/>
            <option value="-Xmx256M"/>
        </bundleapp>
        <delete
            file="/Users/wolf/Work/Tiffany/build/dist/${Name}.app/Contents/PlugIns/jdk1.7.0_11.jdk/Contents/Info.plist"/>
    </target>

Most of tag’s attributes should be obvious, I’m using this build file for both TiffanyScreens version (Pro and Standard), which is what the bid property is used for.

      <property name="Name" value="TiffanyScreens"/>
      <property name="bid" value=""/>
      <property name="name" value="tiffanyscreens"/>
      <property name="Short" value="Tiffany"/>
      <property name="mainclass" value="com.carlsbadcubes.tiffany.TiffanyScreens"/>
      <property name="version" value="5.0"/>

The applicationCategory attribute value should of course match with what was selected as a primary category, when the application entry was created on the iTunes Store. How this value needs to be decoded can be found here.

Since I had selected ‘Productivity’ for the primary category, I used this UTI: public.app-category.productivity

The runtime tag points to the Java_Home location, from where it will pick up the jre.
The classpath tags declare additional locations that need to be put into the bundle as well.
Finally, the option tags will create in the Mac applications Contents/info.plist file.
Once the bundling has happen, as a last thing, I deleted the Java runtime’s info.plist. The submission process seems to be very sensitive about that and only after there was only a single info.plist file left in the bundle was I able to submit the application.

2.5. Application Icon

The Ant target shown above contains two references to the application’s icon, which has to be carefully constructed.
Since the introduction of the “retina display”, Mac application that want to pass the rigorous approval process, need to have an icon file containing icons all the way to 1024×1024 pixels.
After I had created the icon artwork in five resolutions, I carefully named them like so:

  • icon_16x16.png
  • icon_128x128.png
  • icon_256x256.png
  • icon_512x512.png
  • icon512x512@2x.png

A tool provided with the latest XCode release allow to combine the icons into a single icns file:
iconutil -c icns /Users/wolf/Work/Tiffany/res/pro.iconset

2.6. Signing the application bundle

Running the appbundle build target ./build/dist/TiffanyScreens Pro.app, already created a perfectly executable Mac application.

ant appbundle
..
appbundle:
[bundleapp] Creating app bundle: TiffanyScreens Std
[delete] Deleting: /Users/wolf/Work/Tiffany/build/dist/TiffanyScreens Std.app/Contents/PlugIns/jdk1.7.0_11.jdk/Contents/Info.plist

BUILD SUCCESSFUL
Total time: 4 seconds

Before uploading the application for approval, the application and all packaged libraries needed to be signed. Since TiffanyScreens has all its classes and resources in a single jar, this meant that I only needed to sign the TiffanyScreens.jar file and the one jnilib that I included, .. plus all the libraries in the Java runtime of course.

#!/bin/bash
echo "Using ant build-target 'appbundle' to create the app file"
ant appbundle

echo "Sign everything inside the app file"
codesign --entitlements ./tiffany.plist -s "3rd Party Mac Developer Application: Wolf Paulus" -v "./build/dist/TiffanyScreens Pro.app"
find "./build/dist/TiffanyScreens Pro.app/Contents" -type f \( -name "*.jar" -or -name "*.dylib" \) -exec codesign --verbose -f -s "3rd Party Mac Developer Application: Wolf Paulus" --entitlements tiffany.plist {} \;
find "./build/dist/TiffanyScreens Pro.app/Contents" -type f \( -name "*.exe" -or -name "*.jnilib" \) -exec codesign --verbose -f -s "3rd Party Mac Developer Application: Wolf Paulus" --entitlements tiffany.plist {} \;

echo "Verify all libraries have been signed..."
find "./build/dist/TiffanyScreens Pro.app/Contents" -type f \( -name "*.jar" -or -name "*.dylib" \) -exec codesign --verbose --verify {} \;
find "./build/dist/TiffanyScreens Pro.app/Contents" -type f \( -name "*.exe" -or -name "*.jnilib" \) -exec codesign --verbose --verify {} \;

echo "Create signed package"
productbuild --component "./build/dist/TiffanyScreens Pro.app" /Applications --sign "3rd Party Mac Developer Installer: Wolf Paulus" --product tiffany.plist TiffanyScreensPro.pkg

echo "Test pkg before uploading it to Apple"
sudo installer -store -pkg ./TiffanyScreensPro.pkg -target /

echo "more tests"
spctl --assess --verbose=4 --type execute TiffanyScreensPro.pkg
spctl --assess --verbose=4 --type install TiffanyScreensPro.pkg
codesign -v ./build/dist/TiffanyScreens\ Pro.app/

This bash script runs the Ant script, before doing the code signing (using both certificates: “3rd Party Mac Developer Application: Wolf Paulus” and “3rd Party Mac Developer Installer: Wolf Paulus”) and running two sanity checks, recommended before trying to submit the app.

The outcome of this is a *.pkg file, which is made for submission to the app-store approval process but not for installing on your own Mac.

2.7. Submitting for Approval

With the latest version of XCode installed, the Application Loader can be found by, control-clicking XCode and selecting “Show Package Contents”. The Application Loader in the Contents/Applications folder. Selecting “Deliver Your App” automatically picked up all the information I had provided, when creating the application entry in the iTunes Store. Application Loader runs a lot of tests, before finally accepting and uploading the pkg file.
Apploader

Waiting

Now the waiting began. While iTunes Connect (https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa) showed the current status of the approval process, the information provided here http://reviewtimes.shinydevelopment.com/ gave a pretty good indication of the average App Store review time.

A day after submitting Tiffany Screens Pro, I submitted TiffanyScreens Std, which obviously only took a fraction of the time. The only work that I really had to do, was to create that insanely great 1024×1024 icon.

Approved

Each application got approved after 8 days and is now available in the Mac application store. Take a look here:
http://itunes.com/mac/wolfpaulus

mac store wolfpaulus

Tiffanyinstore

1 Comment

  1. Thanks, that’s a very helpful account of the work involved in publishing a Java app to App store.

Submit a Comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>