Category Archives: Uncategorized

Ubuntu phone preview release impressions

Yesterday, Ubuntu releases the first preview version of Ubuntu phone. There are only a few devices on which this can be installed, notable the Samsung Galaxy Nexus and LG Nexus 4 phones. I followed the installation instruction provided by Ubuntu to install on a Nexus 4.

First off, installation is easy. The instructions are sufficiently detailed as long as you have a working Ubuntu system (no installations instructions are provided for the rest of the world). As my interest stems from long time Ubuntu use, this was no problem.

Ubuntu phone

The installation comes with some demo data (contacts, conversations and photos) and a lot of mocked stuff like applications, video and music. It looks and feels nice but there is still a lot of work.

A lot of interactions are done by swiping from the edge of the screen. The left edge is used to select and launch new applications. The right edge allows you to switch between open applications. Swiping from the top allows you to select the notification icons. Most notably to select the “messages” overview. It is quite finicky to select the sound/wifi/battery/time notification icons. Swiping from the bottom show a (quick) menu for the current application. Swiping until you are on the hourglass allows you to get a larger menu (including the option to close an application).

The release notes includes instruction about removing the sample contacts, pictures and videos. Removing the mockup applications did not work for me and I see no details about removing the conversations from the phone application.

It all looks very promising, but this is very clearly a preview version. When putting pictures on the device, the images in subdirectories are not recognized. Just copying music or video to the respective directories is not yet automatically recognized.

The keyboard is currently very simplistic. For example, a more appropriate keyboard could be shown when entering phone details in the contacts, the characters should change between lower and upper case depending on whether shift was pressed, sometimes it is impossible to get rid of the keyboard to allow showing the menu, the enter key seems not to be usable to confirm the value in a field and I don’t know how to move the cursor in the field (specifically when the information is larger than can be displayed).

The camera application is mostly usable though there are some inconsistencies. You cannot zoom in/out by pinching two fingers, the flash does not work and I have had reappearing deleted photos and once where the photo of front and back camera were overlaid in some way.

The home screen (as show in the picture above) only appears when starting the phone. I not found a way to get that back once something else was displayed. The only options seems to be pressing the power button twice. The volume buttons do not work.

The browser is functional and this is also used for some other applications like twitter, gmail and facebook. It unfortunately does not yet include the feature to install a page as application like firefox does on desktop Ubuntu. This may be related to Ubuntu phone being based on Ubuntu 12.10 while this feature may have been introduced in more recent versions on the desktop.

The actual phone application I have not yet been able to test (my SIM card is the wrong size for the phone).

All in all, I am looking forward to seeing the progress on Ubuntu phone.

OSS: mind the license

This article is triggered by my frustrations at and by reading Bruno Lowagie’s post “FOSS and liberal licenses“. Bruno’s post was a reaction to “Open Source, Software Hygiene and STDs” (which includes a link to an old but interesting article about software hygiene).

It is my impression that only few developers are aware of the implications of licenses of the open source libraries they use. Most don”t seem to care. They think that once it is “open source” they can do whatever they want. It is often used as “free as in free beer”. As Richard Stallman pointed out long ago (what is free software), it should be about freedom, about “free as in free speech”.
Unfortunately though, the limitations imposed by many of the open source licenses can limit your freedom a lot. These limitations can be commercially viable, which may serve the users, but this is not always the case.

Before continuing, let me be clear, I am a developer, not a lawyer. This is my interpretation from reading licenses and trying to interpret them. Examples given are from the Java world because that is what I know best. I am focussing on open source libraries and frameworks, especially the commercial implications can be quite different when not embedding the project.

Most applications which use an open source library to not respect the license

Really!

Many licenses (for example BSD style licenses) require you to include a copyright notice. It is unclear to me whether emitting the copyright notice to the console is sufficient. The safe bet (sometimes required) would be to include it in something like an about box or a notices page which can be accessed from anywhere in the application. Some popular Java libraries for which this applies include jdom and xstream.

Some licenses are viral. This especially applies to GPL and AGPL licenses. By including such a library in your software, you are forced to use that license for your software. If your software uses iText (version 5+), it should be open source with AGPL license. If your software uses the MySQL JDBC drivers, it should be GPL.

Not all licenses can be combined (there goes your freedom). This is specifically relevant for the GPL family of licenses (see FSF’s “Various licenses and comments about them“). Sometimes licenses are updated to remedy such incompatibilities (for example Apache License v2 is GPL v3 compatible while earlier versions are not compatible).
A notable example in the Java world is the increasingly popular EPL license which is incompatible with GPL/LGPL.

More complications

When reading the above, it might seem that a very permissive license like MIT or BSD is best for included libraries. Think again. Patent law (especially in the United States, mush less in the European Union – for now, see here) might change that. Maybe the license should include a notice assuring that contributors cannot force you to pay them patent licenses on code they contributed. Maybe the license should try to at least make patent infringements clearer. Some licenses like Apache v2, EPL and the GPL family of licenses seem to do this – though not necessarily in a compatible way, see “Software patents and free software“.

Which license for your project?

There are way too many open source licenses, see the OSI list. Do not create another one!
Like David A. Wheeler, I would recommend using a GPL compatible license as there are many useful libraries in with a license from the GPL family (most JBoss libraries for example).

I would choose one of the following:

  • MIT : very liberal, almost no restrictions (actually ISC is probably even simpler/better, but this is too obscure).
  • Apache (ASLv2) : also very liberal but with some extra constraints regarding patents. These restrictions make me favor it over MIT.
  • Affero GPL (AGPLv3) : a restrictive license which allows you to sell commercial licenses for inclusion in non-free (specifically non-AGPL) software. This can be an advantage if you want to build the software into a business.

I would also recommend that all contributors sign a CLA. This assures copyright is held by one organization which can greatly simplify things when license changes are being discussed or when litigation occurs.

Effect of license on commercial success

Open source software is commercial software. Most projects (especially those with a significant code base) are developed commercially, meaning that at least part of the development work is done by professional developers in their paid time.

Some projects are owned by a company (for example JBoss, iText, Activiti, Geomajas). The company will then use the project to generate sufficient revenue to pay developers to maintain and improve the product.

The license can significantly affect this ability to generate revenue.

  • A more permissive license like Apache or LGPL can enhance the popularity of the product. Developers can often include such libraries in their project without needing permission (or risking blame). Once the library is important in the project, a need for paid support, service level agreements or extra features can arise.
  • A more restrictive license like GPL or AGPL restricts including the library for free, giving the opportunity to sell licenses which are less restrictive.

The advantages of permissive licenses are demonstrated by projects like the JBoss application server and Activiti.

JBoss was originally (ten years ago) used by developers because it was free and faster then the for-pay options of that time. Licenses fees and migration costs opened the door to using JBoss in production, giving the backing company opportunity to sell support contracts and training.

Activiti was started (my interpretation, simplifying) because users (including Alfresco) requested the popular jBPM product to change license from LGPL to Apache. This was denied by the project owner. Alfresco stepped up to build a new BPM engine and managed to lure core jBPM developers to do this. The permissive license allowed other companies to see commercial opportunities in this project allowing a community and successful library to be built very quickly.

The iText case shows a mixture of both approaches. The permissive license from the early versions (in combination with the quality of the product) allowed a large body of users to grow. Even today developers can include the old version in new applications without asking permission growing the user base even more. At some point a more recent version may be needed which forces users (which now rely on the library) to buy a license. Mind you that this would not be a good strategy to follow, some users are alienated by the change of license as they feel they suddenly need to pay for something they were given.

End note

You are a developer. You don’t want your work to be pirated, so please don’t pirate my work either.

If you use software, check the license, see if it is compatible with your project. If you cannot change your project to be compatible (typically by including the necessary notices or changing the license on your software) then either buy a waver (“commercial license”) or don’t use that software!

JIRA workflows, advance parent, clone and reverse link

The JIRA issue tracking system is very powerful. It supports customizable work-flows which can be tuned to allow you to have better control over the issue life-cycle.

When you need additional functions in a work-flow, the Groovy script runner plug-in gives you many useful extra checks and post-functions or allows you to easily add more.

In my case, I needed to tweak some of the provided post-functions.

Transition parent

On a sub-task, it can be useful to force transitions on the parent issue. For example to assure that the parent is marked as “in progress” when progress is started on a sub-task.

Put the following code in $JIRA_INSTALLATION_DIR/atlassian-jira/WEB-INF/classes/com/onresolve/jira/groovy/canned/workflow/postfunctions/TransitionParent.groovy:

package com.onresolve.jira.groovy.canned.workflow.postfunctions

import com.atlassian.jira.ComponentManager
import com.atlassian.jira.config.ConstantsManager
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.util.ErrorCollection
import com.atlassian.jira.workflow.JiraWorkflow
import com.onresolve.jira.groovy.canned.CannedScript
import com.onresolve.jira.groovy.canned.utils.CannedScriptUtils
import com.onresolve.jira.groovy.canned.utils.WorkflowUtils
import com.opensymphony.workflow.loader.ActionDescriptor
import com.opensymphony.workflow.loader.StepDescriptor
import org.apache.log4j.Category
import com.atlassian.crowd.embedded.api.User

class TransitionParent implements CannedScript {

    ComponentManager componentManager = ComponentManager.getInstance()
    Category log = Category.getInstance(ResolveParentAfterSubtasks.class)
    def projectManager = componentManager.getProjectManager()
    public final static String FIELD_PARENTACTION = "FIELD_PARENTACTION"
    public final static String FIELD_RESOLUTION_ID = "FIELD_RESOLUTION_ID"

    String getName() {
        return "Transition parent"
    }

    String getDescription() {
        return """This will do the given action on the parent<br>
        """
    }

    List getCategories() {
        ["Function"]
    }

    Integer getActionId(Issue issue, String actionName) {
        JiraWorkflow workflow = componentManager.getWorkflowManager().getWorkflow(issue)
        StepDescriptor step = workflow.getLinkedStep(issue.status)
        ActionDescriptor ad = step.getActions().find {it.name == actionName} as ActionDescriptor
        ad?.id
    }

    List getParameters(Map params) {
        [
            [
                Name:FIELD_PARENTACTION,
                Label:"Parent action",
                Description:"Choose the action to do on the parent when the sub-tasks are resolved",
                Type: "list",
                Values: CannedScriptUtils.getAllWorkflowActions(false),
            ],
            [
                Label:"Resolution",
                Name:FIELD_RESOLUTION_ID,
                Type: "list",
                Description:"Resolution to use on the parent",
                Values: CannedScriptUtils.getResolutionOptions(true),
            ],
            // todo: need to allow a sub-task resolution
        ]

    }

    public ErrorCollection doValidate(Map params, boolean forPreview) {
        // todo: check this is on a sub-task type
        null
    }

    Map doScript(Map params) {
        log.debug ("TestCondition.doScript with params: ${params}");

        String actionName = params[FIELD_PARENTACTION] as String
        MutableIssue subtask = params['issue'] as MutableIssue
        User user = WorkflowUtils.getUser(params)
        String resolutionId = params[FIELD_RESOLUTION_ID] as String

        log.debug ("actionName: $actionName")
        log.debug ("subtask: $subtask")
        log.debug ("subtask.isSubTask(): ${subtask.isSubTask()}")

        // if this action is resolve and all sub-tasks are resolved
        if (subtask.isSubTask()) {
            MutableIssue parent = subtask.getParentObject() as MutableIssue

            log.debug ("Resolve parent")
            Integer actionId = actionName?.replaceAll(/ .*/, "") as Integer
            if (WorkflowUtils.hasAction(parent, actionId)) {
                WorkflowUtils.resolveIssue(parent, actionId, user, resolutionId, [:])
            } else {
               log.warn("Action name: $actionName not found for this step.")
            }
        }

        return params
    }

    String getDescription(Map params, boolean forPreview) {
        ConstantsManager constantsManager = ComponentManager.getInstance().getConstantsManager()

        StringBuffer sb = new StringBuffer()
        sb << getName() + "<br>Transition " + params[FIELD_PARENTACTION]
        sb.toString()
    }

    public Boolean isFinalParamsPage(Map params) {
        true
    }
}

The script has been submitted for inclusion in future versions of the plug-in, see GRV-123.

Clone and link with reverse link

The normal clone and link post-function always uses the forward link between the issues. The script was patched to allow reverse links as well.

Put the following code in $JIRA_INSTALLATION_DIR/atlassian-jira/WEB-INF/classes/com/onresolve/jira/groovy/canned/workflow/postfunctions/CloneIssue.groovy:

package com.onresolve.jira.groovy.canned.workflow.postfunctions

import com.atlassian.jira.ComponentManager
import com.atlassian.jira.config.ConstantsManager
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.link.IssueLinkManager
import com.atlassian.jira.issue.link.IssueLinkType
import com.atlassian.jira.issue.link.IssueLinkTypeManager
import com.atlassian.jira.util.ErrorCollection
import com.atlassian.jira.util.SimpleErrorCollection
import com.onresolve.jira.groovy.canned.CannedScript
import com.onresolve.jira.groovy.canned.utils.CannedScriptUtils
import com.onresolve.jira.groovy.canned.utils.ConditionUtils

class CloneIssue extends CopyIssueWithAttachments implements CannedScript{

    public static String FIELD_LINK_DIRECTION = "FIELD_LINK_DIRECTION"

    String getName() {
        return "Clones an issue and links."
    }

    String getDescription() {
        return """Clones this issue to another issue, optionally in another project, and optionally a different issue type.
        """
    }

    List getCategories() {
        ["Function", "Listener"]
    }

    List getParameters(Map params) {
        [
            ConditionUtils.getConditionParameter(),
            [
                Name:FIELD_TARGET_PROJECT,
                Label:"Target Project",
                Type: "list",
                Description:"Target project. Leave blank for the same project as the source issue.",
                Values: CannedScriptUtils.getProjectOptions(true),
            ],
            [
                Name:FIELD_TARGET_ISSUE_TYPE,
                Label:"Target Issue Type",
                Type: "list",
                Description:"""Target issue type. Leave blank for the same issue type as the source issue.
                    <br>NOTE: This issue type must be valid for the target project""",
                Values: CannedScriptUtils.getAllIssueTypes(true),
            ],
            getOverridesParam(),
            [
                Name:FIELD_LINK_TYPE,
                Label:'Issue Link Type',
                Type: "list",
                Description:"What link type to use to create a link to the cloned record.",
                Values: CannedScriptUtils.getAllLinkTypes(true),
            ],
            [
                Name:FIELD_LINK_DIRECTION,
                Label:'Link direction',
                Type: "list",
                Description:"What should be the direction of the link",
                Values: [NORMAL:'Normal', REVERSE:'Reverse'],
            ],
        ]
    }

    public ErrorCollection doValidate(Map params, boolean forPreview) {
        SimpleErrorCollection errorCollection = new SimpleErrorCollection()
        if (!params[FIELD_LINK_TYPE]) {
            errorCollection.addError(FIELD_LINK_TYPE, "You must provide a link type.")
        }
        // todo: validation for issue type if set
        return errorCollection
    }

    Map doScript(Map params) {
        MutableIssue issue = params['issue'] as MutableIssue

        Boolean doIt = ConditionUtils.processCondition(params[ConditionUtils.FIELD_CONDITION] as String, issue, false, params)
        if (! doIt) {
            return [:]
        }

        params = super.doScript (params)

        Issue newIssue = params['newIssue'] as Issue

        String linkTypeId = params[FIELD_LINK_TYPE] as String

        // get the current list of outwards depends on links to get the sequence number
        IssueLinkManager linkMgr = ComponentManager.getInstance().getIssueLinkManager()

        if (linkTypeId && linkMgr.isLinkingEnabled()) {
            IssueLinkTypeManager issueLinkTypeManager = (IssueLinkTypeManager) ComponentManager.getComponentInstanceOfType(IssueLinkTypeManager.class)

            IssueLinkType linkType = issueLinkTypeManager.getIssueLinkType(linkTypeId as Long)

            if (linkType) {
                if ("REVERSE".equals(params[FIELD_LINK_DIRECTION])) {
                    linkMgr.createIssueLink (newIssue.genericValue.id, issue.id, linkType.id, 0, getUser(params))
                } else {
                    linkMgr.createIssueLink (issue.id, newIssue.genericValue.id, linkType.id, 0, getUser(params))
                }
            }
            else {
                log.warn ("No link type $linkTypeId found")
            }
        }

        params
    }

    String getDescription(Map params, boolean forPreview) {
        ConstantsManager constantsManager = ComponentManager.getInstance().getConstantsManager()

        StringBuffer sb = new StringBuffer()
        sb << getName() + "<br>Issue will be cloned to project " + (params[FIELD_TARGET_PROJECT] ?: "same as parent")
        sb << "<br>With issue type: " + (params[FIELD_TARGET_ISSUE_TYPE] ? constantsManager.getIssueTypeObject(params[FIELD_TARGET_ISSUE_TYPE] as String)?.name : "same as parent")
        sb.toString()
    }

    public Boolean isFinalParamsPage(Map params) {
        true
    }

}

The patch has been submitted for inclusion in future versions of the plug-in, see GRV-124.