Search

Nautilus - Columns and Property page Provider for APK files

Contents[Hide]

dropcap-gnome-apk

On Android devices, all programs are installed thru some APK packages. Like well-known deb packages, these packages have some specific descriptive data : package name, version number, version code, requested permissions, ...

If you are an APK developper or if you are handling a large collection of APK packages on a Linux computer, you'll find that Nautilus is providing some very poor informations about these files. It is not even displaying the icon properly !

A previous article explained how to Display official APK icon as Nautilus thumbnail.

This article explains how to use Nautilus python extension capabilities to :

  • add some columns providing specific APK informations (package name, version, ...)
  • add one tab to APK file properties to provide a lot of extra informations

This procedure has been tested under Ubuntu Gnome 16.04 LTS and Ubuntu 16.04 LTS running gnome, but it should be applicable to many other modern Gnome Shell based distributions.

1. Install packages and create environment

Under Ubuntu, Nautilus python extensions are not enabled by default.

To enable them, you need to :

  • install python-nautilus package
  • create the directory that will hold the new extensions

Terminal
# sudo apt-get install python-nautilus
# mkdir --parents $HOME/.local/share/nautilus-python/extensions

If not already done, you also need to install aapt utility that will be used to extract data form APK files.

This utility is provided by the Android SDK, but it is also available straight from Ubuntu package repository.

If you don't want to install the full Android SDK, you can simply install it :

Terminal
# sudo apt-get install aapt

You are now ready to create your first Nautilus extension.

2. APK Columns Extension

A Nautilus columns provider is a Python class should provide a least 2 methods :

  • get_columns : called to list the columns provided by the extension
  • update_file_info : called for every file to provide the columns data

APK column provider will generate 4 new columns with APK package most important data :

  • Official name
  • Version
  • Version code
  • Minimum SDK version needed

nautilus-extension-apk-column

All other data will be provided by the property page extension.

As update_file_info will be called for each and every file displayed by Nautilus, it should be optimised for execution time. So the extension way of working will be :

  • one single execution of aapt utility per file
  • analysis of the result line by line thru regular expressions
  • end of analysis as soon as all data are extracted

The following python script will do the job :

~/.local/share/nautilus-python/extensions/apk-columns.py
# ---------------------------------------------------
# Nautilus extension to add APK specific columns
# Procedure :
#   http://bernaerts.dyndns.org/linux/76-gnome/324-gnome-nautilus-apk-column-property-provider-extension
# Depends on :
#   * aapt
# Revision history :
#   08/11/2014, V1.0 - Creation by N. Bernaerts
# ---------------------------------------------------
import subprocess
import urllib
import os
import re
import pipes
from gi.repository import Nautilus, GObject

class ApkColumnExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoProvider):
  def __init__(self):
    pass
    
  def get_data(self,filename):
    apk_name = ""
    apk_versioncode = ""
    apk_versionname = ""
    apk_sdkversion = ""

    command_line = "aapt d badging " + pipes.quote(filename)
    p = subprocess.Popen(command_line , shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    for line in p.stdout.readlines():
      # new line, no match found
      found = False

      # line package:
      if found == False and apk_name == "":
        regexpr = re.match('^package:.*$', line)
        if regexpr: 
          apk_name = re.sub('^.*name=.([^ \']*).*$', '\g<1>', line).rstrip('\n')
          apk_versioncode = re.sub('^.*versionCode=.([^ \']*).*$', '\g<1>', line).rstrip('\n')
          apk_versionname = re.sub('^.*versionName=.([^ \']*).*$', '\g<1>', line).rstrip('\n')
          found = True

      # line sdkVersion:
      if found == False and apk_sdkversion == "":
        regexpr = re.match('^sdkVersion:.*$', line)
        if regexpr: 
          apk_sdkversion = re.sub('^sdkVersion:.([^ \']*).*$', '\g<1>', line).rstrip('\n')
          found = True

      if apk_name != "" and apk_sdkversion != "":
        break

    return apk_name, apk_versioncode, apk_versionname, apk_sdkversion

  def get_columns(self):
      return (
        Nautilus.Column(name="NautilusPython::ApkPkgName", attribute="apk_pkg_name", label="Package", description="Get Name of package"),
        Nautilus.Column(name="NautilusPython::ApkPkgVersion", attribute="apk_pkg_version", label="Version", description="Get Version Name of package"),
        Nautilus.Column(name="NautilusPython::ApkPkgCode", attribute="apk_pkg_code", label="Version\nCode", description="Get Version Code of package"),
        Nautilus.Column(name="NautilusPython::ApkSdkVersion", attribute="apk_sdk_version", label="SDK\nVersion", description="Get Sdk Version of package"),
      )

  def update_file_info(self, file):
    if file.get_uri_scheme() != 'file':
      return
        
    if file.get_mime_type() in ('application/vnd.android.package-archive'):
      filename = urllib.unquote(file.get_uri()[7:])
      result = self.get_data(filename)
    else:
      result=("", "", "", "") 

    file.add_string_attribute('apk_pkg_name', result[0])
    file.add_string_attribute('apk_pkg_code', result[1])
    file.add_string_attribute('apk_pkg_version', result[2])
    file.add_string_attribute('apk_sdk_version', result[3])

You just need to download it under ~/.local/share/nautilus-python/extensions/apk-columns.py and to restart Nautilus to be able to display your new APK specific columns :

Terminal
# wget -O $HOME/.local/share/nautilus-python/extensions/apk-columns.py https://raw.githubusercontent.com/NicolasBernaerts/ubuntu-scripts/master/nautilus/extensions/apk-columns.py
# nautilus -q

You can now add the 4 new APK specific columns.

These new columns are sortable like any other column.

3. APK Property Page Extension

A Nautilus property page provider is a Python class that should provide a least one method called get_property_pages which is called to display a tab in the file property dialog.

So, APK property page provider will generate one Gtk widget in charge of displaying a wide range of data in addition to the previous ones :

  • Target SDK version
  • Supported densities
  • Supported screen families
  • Features used
  • Permissions needed

nautilus-extension-apk-property

The following python script will do the job :

~/.local/share/nautilus-python/extensions/apk-properties.py
# ---------------------------------------------------
# Nautilus extension to add APK properties tab
# Procedure :
#   http://bernaerts.dyndns.org/linux/76-gnome/324-gnome-nautilus-apk-column-property-provider-extension
# Depends on :
#   * aapt
# Revision history :
#   08/11/2014, V1.0 - Creation by N. Bernaerts
# ---------------------------------------------------
import subprocess
import urllib
import os
import re
import pipes
from gi.repository import Nautilus, GObject, Gtk

class ApkPropertyPage(GObject.GObject, Nautilus.PropertyPageProvider):
  def __init__(self):
    pass

  # method to extract data from aapt console 
  def get_data(self,filename):
    apk_name = ""
    apk_versioncode = ""
    apk_versionname = ""
    apk_label = ""
    apk_sdkversion = ""
    apk_targetsdkversion = ""
    apk_supportsscreens = ""
    apk_densities = ""
    apk_features = ""
    apk_permissions = ""

    # aapt command, using pipes to handle filenames including '(', ')', ...
    command_line = "aapt d badging " + pipes.quote(filename)
    p = subprocess.Popen(command_line , shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    # console return analysis
    for line in p.stdout.readlines():

      # new line, no match found
      found = False

      # line package:
      if found == False and apk_name == "":
        regexpr = re.match('^package:.*$', line)
        if regexpr: 
          apk_name = re.sub('^.*name=.([^ \']*).*$', '\g<1>', line).rstrip('\n')
          apk_versioncode = re.sub('^.*versionCode=.([^ \']*).*$', '\g<1>', line).rstrip('\n')
          apk_versionname = re.sub('^.*versionName=.([^ \']*).*$', '\g<1>', line).rstrip('\n')
          found = True

      # line application:
      if found == False and apk_label == "":
        regexpr = re.match('^application:.*$', line)
        if regexpr: 
          apk_label = re.sub('^.*label=.([^ \']*).*$', '\g<1>', line).rstrip('\n')
          found = True
    
      # line sdkVersion:
      if found == False and apk_sdkversion == "":
        regexpr = re.match('^sdkVersion:.*$', line)
        if regexpr: 
          apk_sdkversion = re.sub('^sdkVersion:.([^ \']*).*$', '\g<1>', line).rstrip('\n')
          found = True

      # line targetSdkVersion:
      if found == False and apk_targetsdkversion == "":
        regexpr = re.match('^targetSdkVersion:.*$', line)
        if regexpr: 
          apk_targetsdkversion = re.sub('^targetSdkVersion:.([^ \']*).*$', '\g<1>', line).rstrip('\n')
          found = True

      # line supports-screens:
      if found == False and apk_supportsscreens == "":
        regexpr = re.match('^supports-screens:.*$', line)
        if regexpr: 
          text = re.sub('^supports-screens:.(.*)$', '\g<1>', line)
          text = re.sub(' ', '\n', text)
          apk_supportsscreens = re.sub('\'', '', text).rstrip('\n')
          found = True

      # line densities:
      if found == False and apk_densities == "":
        regexpr = re.match('^densities:.*$', line)
        if regexpr: 
          text = re.sub('^densities:.(.*)$', '\g<1>', line)
          text = re.sub(' ', '\n', text)
          apk_densities = re.sub('\'', '', text).rstrip('\n')
          found = True

      # line uses-feature:
      if found == False:
        regexpr = re.match('^uses-feature:.*$', line)
        if regexpr: 
          apk_features += re.sub('^uses-feature:.(.*).$', '\g<1>', line)
          found = True

      # line uses-permission:
      if found == False:
        regexpr = re.match('^uses-permission:.*$', line)
        if regexpr: 
          apk_permissions += re.sub('^uses-permission:.(.*).$', '\g<1>', line)
          found = True

    # remove last CR/LF
    apk_features = apk_features.rstrip('\n')
    apk_permissions = apk_permissions.rstrip('\n')

    # return data array
    return apk_name, apk_versioncode, apk_versionname, apk_label, apk_sdkversion, apk_targetsdkversion, apk_supportsscreens, apk_densities, apk_features, apk_permissions

  # method to add one property to the table
  def populateTablePosition(self, definition, value, row, column):

    # add definition to table
    label_def = Gtk.Label()
    label_def.set_markup("<b>" + definition + "</b>")
    label_def.set_alignment(1.0, 0)
    label_def.set_padding(10, 0)
    self.table.attach(label_def, column, column + 1, row, row + 1)

    # add value to table
    label_val = Gtk.Label(value)
    label_val.set_alignment(0.0, 0)
    label_val.set_padding(10, 0)
    self.table.attach(label_val, column + 1, column + 2, row, row + 1)
    return

  # method to add one property to properties table
  def populateTableLine(self, definition, value, row):

    # add definition to table
    label_def = Gtk.Label()
    label_def.set_markup("<b>" + definition + "</b>")
    label_def.set_alignment(1.0, 0)
    label_def.set_padding(10, 0)
    self.table.attach(label_def, 0, 1, row, row + 1)

    # add value to table
    label_val = Gtk.Label(value)
    label_val.set_alignment(0.0, 0)
    label_val.set_padding(10, 0)
    self.table.attach(label_val, 1, 4, row, row + 1)
    return

  # method to generate APK properties tab
  def get_property_pages(self, files):
  
    # if dealing with multiple selection, return
    if len(files) != 1:
      return

    # if not dealing with file, return
    file = files[0]
    if file.get_uri_scheme() != 'file':
      return

    # if mimetype corresponds to APK file, read data and populate tab
    if file.get_mime_type() in ('application/vnd.android.package-archive'):
    
      # read data from APK file
      filename = urllib.unquote(file.get_uri()[7:])
      result = self.get_data(filename)

      # create table
      self.table = Gtk.Table(9, 4, False)
      self.table.set_row_spacings(5)

      # populate table
      self.populateTableLine("", "", 0)
      self.populateTablePosition("Label", result[3], 1, 0)
      self.populateTablePosition("Name", result[0], 2, 0)

      self.populateTablePosition("Version name", result[2], 1, 2)
      self.populateTablePosition("Version code", result[1], 2, 2)
      self.populateTablePosition("SDK version", result[4], 3, 2)
      self.populateTablePosition("Target SDK version", result[5], 4, 2)

      self.populateTablePosition("Supported\ndensities", result[7], 4, 0)
      self.populateTablePosition("Supported\nscreens", result[6], 5, 0)
      self.populateTableLine("Features\nused", result[8], 6)
      self.populateTableLine("Permissions\nused", result[9], 7)
      self.populateTableLine("", "", 8)

      # set tab label
      apk_label = Gtk.Label('Details')

      # set tab content (scrolled window -> table)
      apk_win = Gtk.ScrolledWindow()
      apk_win.add_with_viewport(self.table)
      apk_win.show_all()

    # return label and tab content
    return Nautilus.PropertyPage( name="NautilusPython::apk_info", label=apk_label, page=apk_win ),

You just need to download it under ~/.local/share/nautilus-python/extensions/apk-properties.py and to restart Nautilus to be able to display your new APK property tab :

Terminal
# wget -O $HOME/.local/share/nautilus-python/extensions/apk-properties.py https://raw.githubusercontent.com/NicolasBernaerts/ubuntu-scripts/master/nautilus/extensions/apk-properties.py
# nautilus -q

 

Hope it helps.

Signature Technoblog

This article is published "as is", without any warranty that it will work for your specific need.
If you think this article needs some complement, or simply if you think it saved you lots of time & trouble,
just let me know at This email address is being protected from spambots. You need JavaScript enabled to view it.. Cheers !

icon linux icon debian icon apache icon mysql icon php icon piwik icon googleplus