Просмотр исходного кода

Updated sample app with new umbrella aar

Eugene Fryntov 9 лет назад
Родитель
Сommit
13c163a461

+ 2 - 2
MobileLibrary/Android/SampleApps/TunneledWebView/.idea/vcs.xml → MobileLibrary/Android/SampleApps/TunneledWebView/.idea/encodings.xml

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
-  <component name="VcsDirectoryMappings">
-    <mapping directory="" vcs="" />
+  <component name="Encoding">
+    <file url="PROJECT" charset="UTF-8" />
   </component>
 </project>

+ 6 - 0
MobileLibrary/Android/SampleApps/TunneledWebView/.idea/gradle.xml

@@ -13,6 +13,12 @@
             <option value="$PROJECT_DIR$/app" />
           </set>
         </option>
+        <option name="myModules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/app" />
+          </set>
+        </option>
       </GradleProjectSettings>
     </option>
   </component>

+ 8 - 11
MobileLibrary/Android/SampleApps/TunneledWebView/README.md

@@ -10,7 +10,7 @@ an Android app. TunneledWebView proxies a WebView through the Psiphon tunnel.
 Integration
 --------------------------------------------------------------------------------
 
-Uses the [Psiphon Android Library](../../AndroidLibrary/README.md).
+Uses the [Psiphon Android Library](../../../Android/README.md).
 
 Integration is illustrated in the main activity source file in the sample app. Here are the key parts.
 
@@ -53,16 +53,11 @@ import ca.psiphon.PsiphonTunnel;
 //
 // - Add the Psiphon Library AAR module as a dependency (see this app's
 //   project settings; to build this sample project, you need to drop
-//   psi-0.0.10.aar into app/libs).
-//
-// - Use app/src/main/java/ca/psiphon/PsiphonTunnel.java, which provides
-//   a higher-level wrapper around the Psiphon Library module. This file
-//   shows how to use PsiphonTunnel and PsiphonTunnel.TunneledApp.
-//
+//   ca.psiphon.aar into app/libs).
 //----------------------------------------------------------------------------------------------
 
 public class MainActivity extends ActionBarActivity
-        implements PsiphonTunnel.TunneledApp {
+        implements PsiphonTunnel.HostService {
 
 // ...
 
@@ -84,7 +79,9 @@ public class MainActivity extends ActionBarActivity
         // Psiphon running, so start/stop in onCreate/onDestroy or
         // even consider running a background Service.
 
-        if (!mPsiphonTunnel.start("")) {
+        try {
+            mPsiphonTunnel.startTunneling("");
+        } catch (PsiphonTunnel.Exception e) {
             logMessage("failed to start Psiphon");
         }
     }
@@ -114,7 +111,7 @@ public class MainActivity extends ActionBarActivity
 
     private void loadWebView() {
 
-        // NOTE: functions called via PsiphonTunnel.TunneledApp may be
+        // NOTE: functions called via PsiphonTunnel.HostService may be
         // called on background threads. It's important to ensure that
         // these threads are not blocked and that UI functions are not
         // called directly from these threads. Here we use runOnUiThread
@@ -132,7 +129,7 @@ public class MainActivity extends ActionBarActivity
     // ...
 
     //----------------------------------------------------------------------------------------------
-    // PsiphonTunnel.TunneledApp implementation
+    // PsiphonTunnel.HostService implementation
     //
     // NOTE: these are callbacks from the Psiphon Library
     //----------------------------------------------------------------------------------------------

+ 1 - 1
MobileLibrary/Android/SampleApps/TunneledWebView/app/build.gradle

@@ -29,5 +29,5 @@ dependencies {
     compile fileTree(dir: 'libs', include: ['*.jar'])
     testCompile 'junit:junit:4.12'
     compile 'com.android.support:appcompat-v7:21.0.3'
-    compile 'go.psi:psi:0.0.10@aar'
+    compile(name:'ca.psiphon', ext:'aar')
 }

+ 0 - 408
MobileLibrary/Android/SampleApps/TunneledWebView/app/src/main/java/ca/psiphon/PsiphonTunnel.java

@@ -1,408 +0,0 @@
-/*
- * Copyright (c) 2016, Psiphon Inc.
- * All rights reserved.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package ca.psiphon;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.Build;
-import android.telephony.TelephonyManager;
-import android.util.Base64;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import go.psi.Psi;
-
-public class PsiphonTunnel extends Psi.PsiphonProvider.Stub {
-
-    public interface TunneledApp {
-        Context getContext();
-        String getPsiphonConfig();
-        void onDiagnosticMessage(String message);
-        void onAvailableEgressRegions(List<String> regions);
-        void onSocksProxyPortInUse(int port);
-        void onHttpProxyPortInUse(int port);
-        void onListeningSocksProxyPort(int port);
-        void onListeningHttpProxyPort(int port);
-        void onUpstreamProxyError(String message);
-        void onConnecting();
-        void onConnected();
-        void onHomepage(String url);
-        void onClientRegion(String region);
-        void onClientUpgradeDownloaded(String filename);
-        void onSplitTunnelRegion(String region);
-        void onUntunneledAddress(String address);
-        void onBytesTransferred(long sent, long received);
-        void onStartedWaitingForNetworkConnectivity();
-    }
-
-    private final TunneledApp mTunneledApp;
-    private AtomicBoolean mIsWaitingForNetworkConnectivity;
-
-    // Only one PsiphonVpn instance may exist at a time, as the underlying
-    // go.psi.Psi contains global state.
-    private static PsiphonTunnel mPsiphonTunnel;
-
-    public static synchronized PsiphonTunnel newPsiphonTunnel(TunneledApp tunneledApp) {
-        if (mPsiphonTunnel != null) {
-            mPsiphonTunnel.stop();
-        }
-        // Load the native go code embedded in psi.aar
-        System.loadLibrary("gojni");
-        mPsiphonTunnel = new PsiphonTunnel(tunneledApp);
-        return mPsiphonTunnel;
-    }
-
-    private PsiphonTunnel(TunneledApp tunneledApp) {
-        mTunneledApp = tunneledApp;
-        mIsWaitingForNetworkConnectivity = new AtomicBoolean(false);
-    }
-
-    public Object clone() throws CloneNotSupportedException {
-        throw new CloneNotSupportedException();
-    }
-
-    //----------------------------------------------------------------------------------------------
-    // Public API
-    //----------------------------------------------------------------------------------------------
-
-    public synchronized boolean start(String embeddedServerEntries) {
-        return startPsiphon(embeddedServerEntries);
-    }
-
-    public synchronized void stop() {
-        stopPsiphon();
-    }
-
-    //----------------------------------------------------------------------------------------------
-    // PsiphonProvider (Core support) interface implementation
-    //----------------------------------------------------------------------------------------------
-
-    @Override
-    public void Notice(String noticeJSON) {
-        handlePsiphonNotice(noticeJSON);
-    }
-
-    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
-    @Override
-    public void BindToDevice(long fileDescriptor) throws Exception {
-        // This PsiphonProvider function is only called in TunnelWholeDevice mode
-        throw new Exception("BindToDevice not supported");
-    }
-
-    @Override
-    public long HasNetworkConnectivity() {
-        boolean hasConnectivity = hasNetworkConnectivity(mTunneledApp.getContext());
-        boolean wasWaitingForNetworkConnectivity = mIsWaitingForNetworkConnectivity.getAndSet(!hasConnectivity);
-        if (!hasConnectivity && !wasWaitingForNetworkConnectivity) {
-            // HasNetworkConnectivity may be called many times, but only call
-            // onStartedWaitingForNetworkConnectivity once per loss of connectivity,
-            // so the HostService may log a single message.
-            mTunneledApp.onStartedWaitingForNetworkConnectivity();
-        }
-        // TODO: change to bool return value once gobind supports that type
-        return hasConnectivity ? 1 : 0;
-    }
-
-    private static boolean hasNetworkConnectivity(Context context) {
-        ConnectivityManager connectivityManager =
-                (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
-        return networkInfo != null && networkInfo.isConnected();
-    }
-
-    @Override
-    public String GetPrimaryDnsServer() {
-        // This PsiphonProvider function is only called in TunnelWholeDevice mode
-        return "";
-    }
-
-    @Override
-    public String GetSecondaryDnsServer() {
-        // This PsiphonProvider function is only called in TunnelWholeDevice mode
-        return "";
-    }
-
-    //----------------------------------------------------------------------------------------------
-    // Psiphon Tunnel Core
-    //----------------------------------------------------------------------------------------------
-
-    private boolean startPsiphon(String embeddedServerEntries) {
-        stopPsiphon();
-        mTunneledApp.onDiagnosticMessage("starting Psiphon library");
-        try {
-            Psi.Start(
-                    loadPsiphonConfig(mTunneledApp.getContext()),
-                    embeddedServerEntries,
-                    this,
-                    false);
-        } catch (java.lang.Exception e) {
-            mTunneledApp.onDiagnosticMessage("failed to start Psiphon library: " + e.getMessage());
-            return false;
-        }
-        mTunneledApp.onDiagnosticMessage("Psiphon library started");
-        return true;
-    }
-
-    private void stopPsiphon() {
-        mTunneledApp.onDiagnosticMessage("stopping Psiphon library");
-        Psi.Stop();
-        mTunneledApp.onDiagnosticMessage("Psiphon library stopped");
-    }
-
-    private String loadPsiphonConfig(Context context)
-            throws IOException, JSONException {
-
-        // Load settings from the raw resource JSON config file and
-        // update as necessary. Then write JSON to disk for the Go client.
-        JSONObject json = new JSONObject(mTunneledApp.getPsiphonConfig());
-
-        // On Android, these directories must be set to the app private storage area.
-        // The Psiphon library won't be able to use its current working directory
-        // and the standard temporary directories do not exist.
-        json.put("DataStoreDirectory", context.getFilesDir());
-
-        File remoteServerListDownload = new File(context.getFilesDir(), "remote_server_list");
-        json.put("RemoteServerListDownloadFilename", remoteServerListDownload.getPath());
-
-        // Note: onConnecting/onConnected logic assumes 1 tunnel connection
-        json.put("TunnelPoolSize", 1);
-
-        // Continue to run indefinitely until connected
-        json.put("EstablishTunnelTimeoutSeconds", 0);
-
-        // This parameter is for stats reporting
-        json.put("TunnelWholeDevice", 0);
-
-        json.put("EmitBytesTransferred", true);
-
-        json.put("UseIndistinguishableTLS", true);
-
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
-            json.put("UseTrustedCACertificatesForStockTLS", true);
-        }
-
-        try {
-            // Also enable indistinguishable TLS for HTTPS requests that
-            // require system CAs.
-            json.put(
-                    "TrustedCACertificatesFilename",
-                    setupTrustedCertificates(mTunneledApp.getContext()));
-        } catch (Exception e) {
-            mTunneledApp.onDiagnosticMessage(e.getMessage());
-        }
-
-        json.put("DeviceRegion", getDeviceRegion(mTunneledApp.getContext()));
-
-        return json.toString();
-    }
-
-    private void handlePsiphonNotice(String noticeJSON) {
-        try {
-            // All notices are sent on as diagnostic messages
-            // except those that may contain private user data.
-            boolean diagnostic = true;
-
-            JSONObject notice = new JSONObject(noticeJSON);
-            String noticeType = notice.getString("noticeType");
-
-            if (noticeType.equals("Tunnels")) {
-                int count = notice.getJSONObject("data").getInt("count");
-                if (count > 0) {
-                    mTunneledApp.onConnected();
-                } else {
-                    mTunneledApp.onConnecting();
-                }
-
-            } else if (noticeType.equals("AvailableEgressRegions")) {
-                JSONArray egressRegions = notice.getJSONObject("data").getJSONArray("regions");
-                ArrayList<String> regions = new ArrayList<String>();
-                for (int i=0; i<egressRegions.length(); i++) {
-                    regions.add(egressRegions.getString(i));
-                }
-                mTunneledApp.onAvailableEgressRegions(regions);
-
-            } else if (noticeType.equals("SocksProxyPortInUse")) {
-                mTunneledApp.onSocksProxyPortInUse(notice.getJSONObject("data").getInt("port"));
-
-            } else if (noticeType.equals("HttpProxyPortInUse")) {
-                mTunneledApp.onHttpProxyPortInUse(notice.getJSONObject("data").getInt("port"));
-
-            } else if (noticeType.equals("ListeningSocksProxyPort")) {
-                int port = notice.getJSONObject("data").getInt("port");
-                mTunneledApp.onListeningSocksProxyPort(port);
-
-            } else if (noticeType.equals("ListeningHttpProxyPort")) {
-                int port = notice.getJSONObject("data").getInt("port");
-                mTunneledApp.onListeningHttpProxyPort(port);
-
-            } else if (noticeType.equals("UpstreamProxyError")) {
-                mTunneledApp.onUpstreamProxyError(notice.getJSONObject("data").getString("message"));
-
-            } else if (noticeType.equals("ClientUpgradeDownloaded")) {
-                mTunneledApp.onClientUpgradeDownloaded(notice.getJSONObject("data").getString("filename"));
-
-            } else if (noticeType.equals("Homepage")) {
-                mTunneledApp.onHomepage(notice.getJSONObject("data").getString("url"));
-
-            } else if (noticeType.equals("ClientRegion")) {
-                mTunneledApp.onClientRegion(notice.getJSONObject("data").getString("region"));
-
-            } else if (noticeType.equals("SplitTunnelRegion")) {
-                mTunneledApp.onSplitTunnelRegion(notice.getJSONObject("data").getString("region"));
-
-            } else if (noticeType.equals("UntunneledAddress")) {
-                mTunneledApp.onUntunneledAddress(notice.getJSONObject("data").getString("address"));
-
-            } else if (noticeType.equals("BytesTransferred")) {
-                diagnostic = false;
-                JSONObject data = notice.getJSONObject("data");
-                mTunneledApp.onBytesTransferred(data.getLong("sent"), data.getLong("received"));
-            }
-
-            if (diagnostic) {
-                String diagnosticMessage = noticeType + ": " + notice.getJSONObject("data").toString();
-                mTunneledApp.onDiagnosticMessage(diagnosticMessage);
-            }
-
-        } catch (JSONException e) {
-            // Ignore notice
-        }
-    }
-
-    private String setupTrustedCertificates(Context context) throws Exception {
-
-        // Copy the Android system CA store to a local, private cert bundle file.
-        //
-        // This results in a file that can be passed to SSL_CTX_load_verify_locations
-        // for use with OpenSSL modes in tunnel-core.
-        // https://www.openssl.org/docs/manmaster/ssl/SSL_CTX_load_verify_locations.html
-        //
-        // TODO: to use the path mode of load_verify_locations would require emulating
-        // the filename scheme used by c_rehash:
-        // https://www.openssl.org/docs/manmaster/apps/c_rehash.html
-        // http://stackoverflow.com/questions/19237167/the-new-subject-hash-openssl-algorithm-differs
-
-        File directory = context.getDir("PsiphonCAStore", Context.MODE_PRIVATE);
-
-        final String errorMessage = "copy AndroidCAStore failed";
-        try {
-
-            File file = new File(directory, "certs.dat");
-
-            // Pave a fresh copy on every run, which ensures we're not using old certs.
-            // Note: assumes KeyStore doesn't return revoked certs.
-            //
-            // TODO: this takes under 1 second, but should we avoid repaving every time?
-            file.delete();
-
-            PrintStream output = null;
-            try {
-                output = new PrintStream(new FileOutputStream(file));
-
-                KeyStore keyStore;
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
-                    keyStore = KeyStore.getInstance("AndroidCAStore");
-                    keyStore.load(null, null);
-                } else {
-                    keyStore = KeyStore.getInstance("BKS");
-                    FileInputStream inputStream = new FileInputStream("/etc/security/cacerts.bks");
-                    try {
-                        keyStore.load(inputStream, "changeit".toCharArray());
-                    } finally {
-                        if (inputStream != null) {
-                            inputStream.close();
-                        }
-                    }
-                }
-
-                Enumeration<String> aliases = keyStore.aliases();
-                while (aliases.hasMoreElements()) {
-                    String alias = aliases.nextElement();
-                    X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias);
-
-                    output.println("-----BEGIN CERTIFICATE-----");
-                    String pemCert = new String(Base64.encode(cert.getEncoded(), Base64.NO_WRAP), "UTF-8");
-                    // OpenSSL appears to reject the default linebreaking done by Base64.encode,
-                    // so we manually linebreak every 64 characters
-                    for (int i = 0; i < pemCert.length() ; i+= 64) {
-                        output.println(pemCert.substring(i, Math.min(i + 64, pemCert.length())));
-                    }
-                    output.println("-----END CERTIFICATE-----");
-                }
-
-                mTunneledApp.onDiagnosticMessage("prepared PsiphonCAStore");
-
-                return file.getAbsolutePath();
-
-            } finally {
-                if (output != null) {
-                    output.close();
-                }
-            }
-
-        } catch (KeyStoreException e) {
-            throw new Exception(errorMessage, e);
-        } catch (NoSuchAlgorithmException e) {
-            throw new Exception(errorMessage, e);
-        } catch (CertificateException e) {
-            throw new Exception(errorMessage, e);
-        } catch (IOException e) {
-            throw new Exception(errorMessage, e);
-        }
-    }
-
-    private static String getDeviceRegion(Context context) {
-        String region = "";
-        TelephonyManager telephonyManager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
-        if (telephonyManager != null) {
-            region = telephonyManager.getSimCountryIso();
-            if (region.length() == 0 && telephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) {
-                region = telephonyManager.getNetworkCountryIso();
-            }
-        }
-        if (region.length() == 0) {
-            Locale defaultLocale = Locale.getDefault();
-            if (defaultLocale != null) {
-                region = defaultLocale.getCountry();
-            }
-        }
-        return region.toUpperCase(Locale.US);
-    }
-}

+ 35 - 2
MobileLibrary/Android/SampleApps/TunneledWebView/app/src/main/java/ca/psiphon/tunneledwebview/MainActivity.java

@@ -72,7 +72,7 @@ import ca.psiphon.PsiphonTunnel;
 //----------------------------------------------------------------------------------------------
 
 public class MainActivity extends ActionBarActivity
-        implements PsiphonTunnel.TunneledApp {
+        implements PsiphonTunnel.HostService {
 
     private ListView mListView;
     private WebView mWebView;
@@ -110,7 +110,10 @@ public class MainActivity extends ActionBarActivity
         // Psiphon running, so start/stop in onCreate/onDestroy or
         // even consider running a background Service.
 
-        if (!mPsiphonTunnel.start("")) {
+
+        try {
+            mPsiphonTunnel.startTunneling("");
+        } catch (PsiphonTunnel.Exception e) {
             logMessage("failed to start Psiphon");
         }
     }
@@ -170,11 +173,26 @@ public class MainActivity extends ActionBarActivity
     // NOTE: these are callbacks from the Psiphon Library
     //----------------------------------------------------------------------------------------------
 
+    @Override
+    public String getAppName() {
+        return "TunneledWebView Sample";
+    }
+
     @Override
     public Context getContext() {
         return this;
     }
 
+    @Override
+    public Object getVpnService() {
+        return null;
+    }
+
+    @Override
+    public Object newVpnServiceBuilder() {
+        return null;
+    }
+
     @Override
     public String getPsiphonConfig() {
         try {
@@ -252,6 +270,11 @@ public class MainActivity extends ActionBarActivity
         logMessage("client upgrade downloaded");
     }
 
+    @Override
+    public void onClientIsLatestVersion() {
+
+    }
+
     @Override
     public void onSplitTunnelRegion(String region) {
         logMessage("split tunnel region: " + region);
@@ -273,6 +296,16 @@ public class MainActivity extends ActionBarActivity
         logMessage("waiting for network connectivity...");
     }
 
+    @Override
+    public void onClientVerificationRequired(String s, int i, boolean b) {
+
+    }
+
+    @Override
+    public void onExiting() {
+
+    }
+
     @Override
     public void onClientRegion(String region) {
         logMessage("client region: " + region);