Bug 1267141 - Part3 - Extracting clearkey library and info files into data cache folder. draft
authorJames Cheng <jacheng@mozilla.com>
Wed, 18 Jan 2017 11:23:00 +0800
changeset 481625 05fa7220d3ec38f1fedbf786ab2daf9f3d90e732
parent 481624 080b82e19dcf359fc51aa9733ec474e6845cdfa1
child 545252 cb645aa857ac2fb7bbf8063e05e1ab3ed5098286
push id44881
push userbmo:jacheng@mozilla.com
push dateFri, 10 Feb 2017 06:52:44 +0000
bugs1267141
milestone54.0a1
Bug 1267141 - Part3 - Extracting clearkey library and info files into data cache folder. MozReview-Commit-ID: KudWfPYGf6
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
toolkit/mozapps/extensions/internal/GMPProvider.jsm
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -7,16 +7,18 @@ package org.mozilla.gecko;
 
 import android.Manifest;
 import android.annotation.TargetApi;
 import android.app.DownloadManager;
 import android.content.ContentProviderClient;
 import android.os.Environment;
 import android.os.Process;
 import android.support.annotation.NonNull;
+import android.system.Os;
+import android.system.ErrnoException;
 
 import android.graphics.Rect;
 
 import org.json.JSONArray;
 import org.mozilla.gecko.activitystream.ActivityStream;
 import org.mozilla.gecko.adjust.AdjustBrowserAppDelegate;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.AppConstants.Versions;
@@ -56,16 +58,17 @@ import org.mozilla.gecko.home.HomePager.
 import org.mozilla.gecko.home.HomePanelsManager;
 import org.mozilla.gecko.home.HomeScreen;
 import org.mozilla.gecko.home.SearchEngine;
 import org.mozilla.gecko.icons.Icons;
 import org.mozilla.gecko.javaaddons.JavaAddonManager;
 import org.mozilla.gecko.media.VideoPlayer;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuItem;
+import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.gecko.notifications.NotificationHelper;
 import org.mozilla.gecko.overlays.ui.ShareDialog;
 import org.mozilla.gecko.permissions.Permissions;
 import org.mozilla.gecko.preferences.ClearOnShutdownPref;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.promotion.AddToHomeScreenPromotion;
 import org.mozilla.gecko.delegates.BookmarkStateChangeDelegate;
@@ -97,18 +100,20 @@ import org.mozilla.gecko.updater.PostUpd
 import org.mozilla.gecko.updater.UpdateServiceHelper;
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.ContextUtils;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.GeckoBundle;
+import org.mozilla.gecko.util.GeckoJarReader;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.IntentUtils;
+import org.mozilla.gecko.util.IOUtils;
 import org.mozilla.gecko.util.MenuUtils;
 import org.mozilla.gecko.util.PrefUtils;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.AnchoredPopup;
 
 import org.mozilla.gecko.widget.GeckoActionProvider;
 
@@ -582,16 +587,104 @@ public class BrowserApp extends GeckoApp
         }
 
         if (AndroidGamepadManager.handleKeyEvent(event)) {
             return true;
         }
         return super.onKeyUp(keyCode, event);
     }
 
+    private void ensureParentDirExist(final File file) throws Exception {
+        final File oParentDir = file.getParentFile();
+        if (!oParentDir.exists()) {
+            Log.i(LOGTAG, "Creating " + oParentDir.getAbsolutePath());
+            if (!oParentDir.mkdirs()) {
+                final String description = "Unable to create directories: " + oParentDir.getAbsolutePath();
+                throw new Exception(description);
+            }
+        }
+    }
+
+    /**
+    * Copies files under /assets/@ANDROID_CPU_ARCH@/PLUGIN_NAME/PLUGIN_VER out
+    * of the APK and into the app's cache directory.
+    */
+    private void extractClearKeyFiles(final String dstClearkeyPath) {
+        File dstDirFile = new File(dstClearkeyPath);
+        File dstParentDirFile = new File(dstDirFile.getParent());
+        Log.i(LOGTAG, " extracting clearkey files to ... " + dstClearkeyPath + " . parent = " + dstDirFile.getParent());
+        final String plugInName = dstParentDirFile.getName();
+        final String plugInVersion = dstDirFile.getName();
+        String schemeName = "";
+        if (plugInName.startsWith("gmp-")) {
+            schemeName = plugInName.substring(4);
+        } else {
+            GeckoAppShell.notifyObservers("EME:ExtractClearkeyDone", "");
+            return;
+        }
+        final String libName = "lib" + schemeName + ".so";
+        final String infoName = "manifest.json";
+        final String pkgPath = getContext().getPackageResourcePath();
+        final String libPath = plugInName + File.separator +
+                               plugInVersion + File.separator +
+                               libName;
+        final String assetsInfoPath = "assets" + File.separator +
+                                      android.os.Build.CPU_ABI + File.separator +
+                                      plugInName + File.separator +
+                                      plugInVersion + File.separator +
+                                      infoName;
+
+        final String srcinfoPath = "jar:file://" + pkgPath + "!" + File.separator + assetsInfoPath;
+        final String dstLibPath = dstClearkeyPath + File.separator + libName;
+        final String dstInfoPath = dstClearkeyPath + File.separator + infoName;
+
+        try {
+
+            try {
+                // Extract compressed libclearkey.so file to cache foler.
+                GeckoLoader.extractGeckoLibs(getContext(),
+                                             pkgPath,
+                                             libPath);
+                // Create symlink to the gmp-clearkey folder if needed.
+                final File oDstLibFile = new File(dstLibPath);
+                if (!oDstLibFile.exists()) {
+                  try {
+                      ensureParentDirExist(oDstLibFile);
+                      Os.symlink(getContext().getCacheDir().getAbsolutePath() + File.separator + libName,
+                          dstLibPath);
+                  } catch(ErrnoException e) {
+                      Log.i(LOGTAG, "Error create symlink failed." + e);
+                  }
+                }
+            } catch (Exception e) {
+                Log.i(LOGTAG, "Error extracting '" + libPath + "' from APK to dst...e = " + e);
+            }
+
+            final File oInfoFile = new File(dstInfoPath);
+            if (!oInfoFile.exists()) {
+                // Extract compressed .info file to destination path.
+                java.io.InputStream inStream = GeckoJarReader.getStream(getContext(),
+                                                                        srcinfoPath);
+                final java.io.OutputStream outStream = new java.io.FileOutputStream(oInfoFile);
+                try {
+                    IOUtils.copy(inStream, outStream);
+                } catch (IOException e) {
+                    Log.i(LOGTAG, "Error copying '" + srcinfoPath + "' from APK to dst...e = " + e);
+                } finally {
+                    IOUtils.safeStreamClose(inStream);
+                    IOUtils.safeStreamClose(outStream);
+                }
+            }
+            GeckoAppShell.notifyObservers("EME:ExtractClearkeyDone", dstClearkeyPath);
+        } catch (Throwable e) {
+            Log.i(LOGTAG, "Error extracting Clearkey related files, e :" + e);
+            GeckoAppShell.notifyObservers("EME:ExtractClearkeyDone", "");
+        }
+    }
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         if (!HardwareUtils.isSupportedSystem()) {
             // This build does not support the Android version of the device; Exit early.
             super.onCreate(savedInstanceState);
             return;
         }
 
@@ -748,16 +841,17 @@ public class BrowserApp extends GeckoApp
             "CharEncoding:Data",
             "CharEncoding:State",
             "Settings:Show",
             "Updater:Launch",
             "Sanitize:OpenTabs",
             null);
 
         EventDispatcher.getInstance().registerBackgroundThreadListener(this,
+            "EME:ExtractClearkey",
             "Experiments:GetActive",
             "Experiments:SetOverride",
             "Experiments:ClearOverride",
             "Favicon:Request",
             "Feedback:MaybeLater",
             "Sanitize:ClearHistory",
             "Sanitize:ClearSyncedTabs",
             "Telemetry:Gather",
@@ -1440,16 +1534,17 @@ public class BrowserApp extends GeckoApp
         if (mZoomedView != null) {
             mZoomedView.destroy();
         }
 
         mSearchEngineManager.unregisterListeners();
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
             "Search:Keyword",
+            "EME:ExtractClearkey",
             null);
 
         EventDispatcher.getInstance().unregisterUiThreadListener(this,
             "Menu:Open",
             "Menu:Update",
             "Menu:Add",
             "Menu:Remove",
             "LightweightTheme:Update",
@@ -1775,16 +1870,21 @@ public class BrowserApp extends GeckoApp
                     // TODO: Better scheduling of DLC actions (Bug 1257492)
                     DownloadContentService.startSync(this);
                     DownloadContentService.startVerification(this);
                 }
 
                 FeedService.setup(this);
                 break;
 
+            case "EME:ExtractClearkey":
+                final String dstClearkeyDir = message.getString("targetpath");
+                extractClearKeyFiles(dstClearkeyDir);
+                break;
+
             case "Menu:Open":
                 if (mBrowserToolbar.isEditing()) {
                     mBrowserToolbar.cancelEdit();
                 }
                 openOptionsMenu();
                 break;
 
             case "Menu:Update":
--- a/toolkit/mozapps/extensions/internal/GMPProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/GMPProvider.jsm
@@ -23,16 +23,18 @@ Cu.import("resource://gre/modules/GMPUti
 /* globals GMP_PLUGIN_IDS, GMPPrefs, GMPUtils, OPEN_H264_ID, WIDEVINE_ID */
 Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/UpdateUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(
   this, "GMPInstallManager", "resource://gre/modules/GMPInstallManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(
   this, "setTimeout", "resource://gre/modules/Timer.jsm");
+XPCOMUtils.defineLazyModuleGetter(
+  this, "Messaging", "resource://gre/modules/Messaging.jsm");
 
 const URI_EXTENSION_STRINGS  = "chrome://mozapps/locale/extensions/extensions.properties";
 const STRING_TYPE_NAME       = "type.%ID%.name";
 
 const SEC_IN_A_DAY           = 24 * 60 * 60;
 // How long to wait after a user enabled EME before attempting to download CDMs.
 const GMP_CHECK_DELAY        = 10 * 1000; // milliseconds
 
@@ -518,20 +520,40 @@ GMPWrapper.prototype = {
   },
 };
 
 var GMPProvider = {
   get name() { return "GMPProvider"; },
 
   _plugins: null,
 
+  observe: function observe(aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case "EME:ExtractClearkeyDone":
+        this._log.info("Receiving topic - 'EME:ExtractClearkeyDone'");
+        if (aData) {
+          gmpService.addPluginDirectory(aData);
+        } else {
+          this._log.info("EME:ExtractClearkeyDone - adding gmp directory failed ");
+        }
+        break;
+      default:
+        break;
+    }
+  },
+
   startup() {
+
     configureLogging();
     this._log = Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP",
                                                           "GMPProvider.");
+    if (AppConstants.platform === "android") {
+      Services.obs.addObserver(this, "EME:ExtractClearkeyDone", false);
+    }
+
     this.buildPluginList();
     this.ensureProperCDMInstallState();
 
     Preferences.observe(GMPPrefs.KEY_LOG_BASE, configureLogging);
 
     for (let plugin of this._plugins.values()) {
       let wrapper = plugin.wrapper;
       let gmpPath = wrapper.gmpPath;
@@ -563,29 +585,46 @@ var GMPProvider = {
                          e.name + " - sandboxing not available?", e);
         }
       }
     }
 
     try {
       let greDir = Services.dirsvc.get(NS_GRE_DIR,
                                        Ci.nsILocalFile);
-      let clearkeyPath = OS.Path.join(greDir.path,
-                                      CLEARKEY_PLUGIN_ID,
-                                      CLEARKEY_VERSION);
-      this._log.info("startup - adding clearkey CDM directory " +
-                     clearkeyPath);
-      gmpService.addPluginDirectory(clearkeyPath);
+      let clearkeyPath;
+      if (AppConstants.platform === "android") {
+        clearkeyPath = OS.Path.join(greDir.path,
+                                    "cache",
+                                    CLEARKEY_PLUGIN_ID,
+                                    CLEARKEY_VERSION);
+        this._log.info("startup - request clearkey lib extraction " +
+                       clearkeyPath);
+        Messaging.sendRequest({type: "EME:ExtractClearkey",
+                               targetpath: clearkeyPath });
+      } else {
+        clearkeyPath = OS.Path.join(greDir.path,
+                                    CLEARKEY_PLUGIN_ID,
+                                    CLEARKEY_VERSION);
+        this._log.info("startup - adding clearkey CDM directory " +
+                       clearkeyPath);
+        gmpService.addPluginDirectory(clearkeyPath);
+      }
     } catch (e) {
       this._log.warn("startup - adding clearkey CDM failed", e);
     }
   },
 
   shutdown() {
     this._log.trace("shutdown");
+
+    if (AppConstants.platform === "android") {
+      Services.obs.removeObserver(this, "EME:ExtractClearkeyDone");
+    }
+
     Preferences.ignore(GMPPrefs.KEY_LOG_BASE, configureLogging);
 
     let shutdownTask = Task.spawn(function*() {
       this._log.trace("shutdown - shutdownTask");
       let shutdownSucceeded = true;
 
       for (let plugin of this._plugins.values()) {
         try {