big check-in of major gradle refactor; now building and running!
VPN features are disabled for now
|
@ -1,11 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.torproject.android"
|
||||
android:versionName="15.1.3-beta"
|
||||
android:versionCode="15130000"
|
||||
android:versionName="15.2.0-alpha-1"
|
||||
android:versionCode="15200001"
|
||||
android:installLocation="auto"
|
||||
>
|
||||
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="23"/>
|
||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="23"/>
|
||||
<!--
|
||||
<permission android:name="org.torproject.android.MANAGE_TOR"
|
||||
android:label="@string/permission_manage_tor_label"
|
||||
|
@ -103,7 +103,7 @@
|
|||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="org.torproject.android.service.OnBootReceiver"
|
||||
<receiver android:name="org.torproject.android.OnBootReceiver"
|
||||
android:enabled="true" android:exported="true"
|
||||
|
||||
>
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.2"
|
||||
|
||||
sourceSets.main.jni.srcDirs = []
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.torproject.android"
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 23
|
||||
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':jsocksAndroid')
|
||||
compile project(':orbotservice')
|
||||
compile 'com.android.support:support-v4:23.4.0'
|
||||
compile 'com.android.support:appcompat-v7:23.4.0'
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.torproject.android"
|
||||
android:versionName="15.2.0-alpha-1"
|
||||
android:versionCode="15200001"
|
||||
android:installLocation="auto"
|
||||
>
|
||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="23"/>
|
||||
<!--
|
||||
<permission android:name="org.torproject.android.MANAGE_TOR"
|
||||
android:label="@string/permission_manage_tor_label"
|
||||
android:description="@string/permission_manage_tor_description"
|
||||
android:protectionLevel="signature"/>
|
||||
|
||||
<uses-permission android:name="org.torproject.android.MANAGE_TOR"/>
|
||||
-->
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<application android:name="org.torproject.android.OrbotApp" android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:description="@string/app_description"
|
||||
android:configChanges="locale|orientation|screenSize"
|
||||
android:theme="@style/DefaultTheme"
|
||||
android:allowBackup="false"
|
||||
android:allowClearUserData="true"
|
||||
android:persistent="true"
|
||||
android:stopWithTask="false"
|
||||
android:largeHeap="false"
|
||||
>
|
||||
|
||||
<activity android:name=".OrbotMainActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTop"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="bridge" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<action android:name="org.torproject.android.REQUEST_HS_PORT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<action android:name="org.torproject.android.START_TOR" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<!--
|
||||
This is for ensuring the background service still runs when/if the app is swiped away
|
||||
-->
|
||||
<activity
|
||||
android:name=".service.DummyActivity"
|
||||
android:theme="@android:style/Theme.Translucent"
|
||||
android:enabled="true"
|
||||
android:allowTaskReparenting="true"
|
||||
android:noHistory="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:alwaysRetainTaskState="false"
|
||||
android:stateNotNeeded="true"
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:finishOnTaskLaunch="true"
|
||||
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".vpn.VPNEnableActivity" android:label="@string/app_name" android:exported="false"
|
||||
android:theme="@android:style/Theme.Translucent"
|
||||
/>
|
||||
|
||||
|
||||
<activity android:name="org.torproject.android.ui.PromoAppsActivity" android:exported="false"/>
|
||||
|
||||
|
||||
<activity android:name=".settings.SettingsPreferences" android:label="@string/app_name"/>
|
||||
<activity android:name=".ui.AppManager" android:label="@string/app_name"/>
|
||||
|
||||
<service
|
||||
android:name=".service.TorService"
|
||||
android:enabled="true"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:stopWithTask="false" >
|
||||
</service>
|
||||
|
||||
<receiver
|
||||
android:name=".service.StartTorReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="org.torproject.android.intent.action.START" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".OnBootReceiver"
|
||||
android:enabled="true" android:exported="true"
|
||||
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<category android:name="android.intent.category.HOME" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||
<category android:name="android.intent.category.HOME" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_MOUNTED"/>
|
||||
<category android:name="android.intent.category.HOME" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!--
|
||||
<service android:name="org.torproject.android.vpn.OrbotVpnService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
-->
|
||||
</application>
|
||||
</manifest>
|
|
@ -0,0 +1,22 @@
|
|||
obfs3 83.212.101.3:80 A09D536DD1752D542E1FBB3C9CE4449D51298239
|
||||
obfs3 169.229.59.74:31493 AF9F66B7B04F8FF6F32D455F05135250A16543C9
|
||||
obfs3 169.229.59.75:46328 AF9F66B7B04F8FF6F32D455F05135250A16543C9
|
||||
obfs3 109.105.109.163:38980 1E05F577A0EC0213F971D81BF4D86A9E4E8229ED
|
||||
obfs3 109.105.109.163:47779 4C331FA9B3D1D6D8FB0D8FBBF0C259C360D97E6A
|
||||
scramblesuit 83.212.101.3:443 A09D536DD1752D542E1FBB3C9CE4449D51298239 password=XTCXLG2JAMJKZW2POLBAOWOQETQSMASH
|
||||
obfs4 198.245.60.50:443 752CF7825B3B9EA6A98C83AC41F7099D67007EA5 cert=xpmQtKUqQ/6v5X7ijgYE/f03+l2/EuQ1dexjyUhh16wQlu/cpXUGalmhDIlhuiQPNEKmKw iat-mode=0
|
||||
obfs4 109.105.109.165:10527 8DFCD8FB3285E855F5A55EDDA35696C743ABFC4E cert=Bvg/itxeL4TWKLP6N1MaQzSOC6tcRIBv6q57DYAZc3b2AzuM+/TfB7mqTFEfXILCjEwzVA iat-mode=0
|
||||
obfs4 83.212.101.3:41213 A09D536DD1752D542E1FBB3C9CE4449D51298239 cert=lPRQ/MXdD1t5SRZ9MquYQNT9m5DV757jtdXdlePmRCudUU9CFUOX1Tm7/meFSyPOsud7Cw iat-mode=0
|
||||
obfs4 104.131.108.182:56880 EF577C30B9F788B0E1801CF7E433B3B77792B77A cert=0SFhfDQrKjUJP8Qq6wrwSICEPf3Vl/nJRsYxWbg3QRoSqhl2EB78MPS2lQxbXY4EW1wwXA iat-mode=0
|
||||
obfs4 109.105.109.147:13764 BBB28DF0F201E706BE564EFE690FE9577DD8386D cert=KfMQN/tNMFdda61hMgpiMI7pbwU1T+wxjTulYnfw+4sgvG0zSH7N7fwT10BI8MUdAD7iJA iat-mode=0
|
||||
obfs4 154.35.22.10:41835 8FB9F4319E89E5C6223052AA525A192AFBC85D55 cert=GGGS1TX4R81m3r0HBl79wKy1OtPPNR2CZUIrHjkRg65Vc2VR8fOyo64f9kmT1UAFG7j0HQ iat-mode=0
|
||||
obfs4 154.35.22.11:49868 A832D176ECD5C7C6B58825AE22FC4C90FA249637 cert=YPbQqXPiqTUBfjGFLpm9JYEFTBvnzEJDKJxXG5Sxzrr/v2qrhGU4Jls9lHjLAhqpXaEfZw iat-mode=0
|
||||
obfs4 154.35.22.12:80 00DC6C4FA49A65BD1472993CF6730D54F11E0DBB cert=N86E9hKXXXVz6G7w2z8wFfhIDztDAzZ/3poxVePHEYjbKDWzjkRDccFMAnhK75fc65pYSg iat-mode=0
|
||||
obfs4 154.35.22.13:443 FE7840FE1E21FE0A0639ED176EDA00A3ECA1E34D cert=fKnzxr+m+jWXXQGCaXe4f2gGoPXMzbL+bTBbXMYXuK0tMotd+nXyS33y2mONZWU29l81CA iat-mode=0
|
||||
obfs4 154.35.22.10:1984 8FB9F4319E89E5C6223052AA525A192AFBC85D55 cert=GGGS1TX4R81m3r0HBl79wKy1OtPPNR2CZUIrHjkRg65Vc2VR8fOyo64f9kmT1UAFG7j0HQ iat-mode=0
|
||||
obfs4 154.35.22.11:1984 A832D176ECD5C7C6B58825AE22FC4C90FA249637 cert=YPbQqXPiqTUBfjGFLpm9JYEFTBvnzEJDKJxXG5Sxzrr/v2qrhGU4Jls9lHjLAhqpXaEfZw iat-mode=0
|
||||
obfs4 154.35.22.12:1984 00DC6C4FA49A65BD1472993CF6730D54F11E0DBB cert=N86E9hKXXXVz6G7w2z8wFfhIDztDAzZ/3poxVePHEYjbKDWzjkRDccFMAnhK75fc65pYSg iat-mode=0
|
||||
obfs4 154.35.22.13:1984 FE7840FE1E21FE0A0639ED176EDA00A3ECA1E34D cert=fKnzxr+m+jWXXQGCaXe4f2gGoPXMzbL+bTBbXMYXuK0tMotd+nXyS33y2mONZWU29l81CA iat-mode=0
|
||||
meek 0.0.2.0:1 46D4A71197B8FA515A826C6B017C522FE264655B url=https://meek-reflect.appspot.com/ front=www.google.com
|
||||
meek 0.0.2.0:2 B9E7141C594AF25699E0079C1F0146F409495296 url=https://d2zfqthxsdq309.cloudfront.net/ front=a0.awsstatic.com
|
||||
meek 0.0.2.0:3 A2C13B7DFCAB1CBF3A884B6EB99A98067AB6EF44 url=https://az786092.vo.msecnd.net/ front=ajax.aspnetcdn.com
|
|
@ -0,0 +1,506 @@
|
|||
/*
|
||||
* Copyright 2009 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.integration.android;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Fragment;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* <p>A utility class which helps ease integration with Barcode Scanner via {@link Intent}s. This is a simple
|
||||
* way to invoke barcode scanning and receive the result, without any need to integrate, modify, or learn the
|
||||
* project's source code.</p>
|
||||
*
|
||||
* <h2>Initiating a barcode scan</h2>
|
||||
*
|
||||
* <p>To integrate, create an instance of {@code IntentIntegrator} and call {@link #initiateScan()} and wait
|
||||
* for the result in your app.</p>
|
||||
*
|
||||
* <p>It does require that the Barcode Scanner (or work-alike) application is installed. The
|
||||
* {@link #initiateScan()} method will prompt the user to download the application, if needed.</p>
|
||||
*
|
||||
* <p>There are a few steps to using this integration. First, your {@link Activity} must implement
|
||||
* the method {@link Activity#onActivityResult(int, int, Intent)} and include a line of code like this:</p>
|
||||
*
|
||||
* <pre>{@code
|
||||
* public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
* IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
||||
* if (scanResult != null) {
|
||||
* // handle scan result
|
||||
* }
|
||||
* // else continue with any other code you need in the method
|
||||
* ...
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <p>This is where you will handle a scan result.</p>
|
||||
*
|
||||
* <p>Second, just call this in response to a user action somewhere to begin the scan process:</p>
|
||||
*
|
||||
* <pre>{@code
|
||||
* IntentIntegrator integrator = new IntentIntegrator(yourActivity);
|
||||
* integrator.initiateScan();
|
||||
* }</pre>
|
||||
*
|
||||
* <p>Note that {@link #initiateScan()} returns an {@link AlertDialog} which is non-null if the
|
||||
* user was prompted to download the application. This lets the calling app potentially manage the dialog.
|
||||
* In particular, ideally, the app dismisses the dialog if it's still active in its {@link Activity#onPause()}
|
||||
* method.</p>
|
||||
*
|
||||
* <p>You can use {@link #setTitle(String)} to customize the title of this download prompt dialog (or, use
|
||||
* {@link #setTitleByID(int)} to set the title by string resource ID.) Likewise, the prompt message, and
|
||||
* yes/no button labels can be changed.</p>
|
||||
*
|
||||
* <p>Finally, you can use {@link #addExtra(String, Object)} to add more parameters to the Intent used
|
||||
* to invoke the scanner. This can be used to set additional options not directly exposed by this
|
||||
* simplified API.</p>
|
||||
*
|
||||
* <p>By default, this will only allow applications that are known to respond to this intent correctly
|
||||
* do so. The apps that are allowed to response can be set with {@link #setTargetApplications(List)}.
|
||||
* For example, set to {@link #TARGET_BARCODE_SCANNER_ONLY} to only target the Barcode Scanner app itself.</p>
|
||||
*
|
||||
* <h2>Sharing text via barcode</h2>
|
||||
*
|
||||
* <p>To share text, encoded as a QR Code on-screen, similarly, see {@link #shareText(CharSequence)}.</p>
|
||||
*
|
||||
* <p>Some code, particularly download integration, was contributed from the Anobiit application.</p>
|
||||
*
|
||||
* <h2>Enabling experimental barcode formats</h2>
|
||||
*
|
||||
* <p>Some formats are not enabled by default even when scanning with {@link #ALL_CODE_TYPES}, such as
|
||||
* PDF417. Use {@link #initiateScan(java.util.Collection)} with
|
||||
* a collection containing the names of formats to scan for explicitly, like "PDF_417", to use such
|
||||
* formats.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @author Fred Lin
|
||||
* @author Isaac Potoczny-Jones
|
||||
* @author Brad Drehmer
|
||||
* @author gcstang
|
||||
*/
|
||||
public class IntentIntegrator {
|
||||
|
||||
public static final int REQUEST_CODE = 0x0000c0de; // Only use bottom 16 bits
|
||||
private static final String TAG = IntentIntegrator.class.getSimpleName();
|
||||
|
||||
public static final String DEFAULT_TITLE = "Install Barcode Scanner?";
|
||||
public static final String DEFAULT_MESSAGE =
|
||||
"This application requires Barcode Scanner. Would you like to install it?";
|
||||
public static final String DEFAULT_YES = "Yes";
|
||||
public static final String DEFAULT_NO = "No";
|
||||
|
||||
private static final String BS_PACKAGE = "com.google.zxing.client.android";
|
||||
private static final String BSPLUS_PACKAGE = "com.srowen.bs.android";
|
||||
|
||||
// supported barcode formats
|
||||
public static final Collection<String> PRODUCT_CODE_TYPES = list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "RSS_14");
|
||||
public static final Collection<String> ONE_D_CODE_TYPES =
|
||||
list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "CODE_39", "CODE_93", "CODE_128",
|
||||
"ITF", "RSS_14", "RSS_EXPANDED");
|
||||
public static final Collection<String> QR_CODE_TYPES = Collections.singleton("QR_CODE");
|
||||
public static final Collection<String> DATA_MATRIX_TYPES = Collections.singleton("DATA_MATRIX");
|
||||
|
||||
public static final Collection<String> ALL_CODE_TYPES = null;
|
||||
|
||||
public static final List<String> TARGET_BARCODE_SCANNER_ONLY = Collections.singletonList(BS_PACKAGE);
|
||||
public static final List<String> TARGET_ALL_KNOWN = list(
|
||||
BSPLUS_PACKAGE, // Barcode Scanner+
|
||||
BSPLUS_PACKAGE + ".simple", // Barcode Scanner+ Simple
|
||||
BS_PACKAGE // Barcode Scanner
|
||||
// What else supports this intent?
|
||||
);
|
||||
|
||||
private final Activity activity;
|
||||
private final Fragment fragment;
|
||||
|
||||
private String title;
|
||||
private String message;
|
||||
private String buttonYes;
|
||||
private String buttonNo;
|
||||
private List<String> targetApplications;
|
||||
private final Map<String,Object> moreExtras = new HashMap<String,Object>(3);
|
||||
|
||||
/**
|
||||
* @param activity {@link Activity} invoking the integration
|
||||
*/
|
||||
public IntentIntegrator(Activity activity) {
|
||||
this.activity = activity;
|
||||
this.fragment = null;
|
||||
initializeConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fragment {@link Fragment} invoking the integration.
|
||||
* {@link #startActivityForResult(Intent, int)} will be called on the {@link Fragment} instead
|
||||
* of an {@link Activity}
|
||||
*/
|
||||
public IntentIntegrator(Fragment fragment) {
|
||||
this.activity = fragment.getActivity();
|
||||
this.fragment = fragment;
|
||||
initializeConfiguration();
|
||||
}
|
||||
|
||||
private void initializeConfiguration() {
|
||||
title = DEFAULT_TITLE;
|
||||
message = DEFAULT_MESSAGE;
|
||||
buttonYes = DEFAULT_YES;
|
||||
buttonNo = DEFAULT_NO;
|
||||
targetApplications = TARGET_ALL_KNOWN;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public void setTitleByID(int titleID) {
|
||||
title = activity.getString(titleID);
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public void setMessageByID(int messageID) {
|
||||
message = activity.getString(messageID);
|
||||
}
|
||||
|
||||
public String getButtonYes() {
|
||||
return buttonYes;
|
||||
}
|
||||
|
||||
public void setButtonYes(String buttonYes) {
|
||||
this.buttonYes = buttonYes;
|
||||
}
|
||||
|
||||
public void setButtonYesByID(int buttonYesID) {
|
||||
buttonYes = activity.getString(buttonYesID);
|
||||
}
|
||||
|
||||
public String getButtonNo() {
|
||||
return buttonNo;
|
||||
}
|
||||
|
||||
public void setButtonNo(String buttonNo) {
|
||||
this.buttonNo = buttonNo;
|
||||
}
|
||||
|
||||
public void setButtonNoByID(int buttonNoID) {
|
||||
buttonNo = activity.getString(buttonNoID);
|
||||
}
|
||||
|
||||
public Collection<String> getTargetApplications() {
|
||||
return targetApplications;
|
||||
}
|
||||
|
||||
public final void setTargetApplications(List<String> targetApplications) {
|
||||
if (targetApplications.isEmpty()) {
|
||||
throw new IllegalArgumentException("No target applications");
|
||||
}
|
||||
this.targetApplications = targetApplications;
|
||||
}
|
||||
|
||||
public void setSingleTargetApplication(String targetApplication) {
|
||||
this.targetApplications = Collections.singletonList(targetApplication);
|
||||
}
|
||||
|
||||
public Map<String,?> getMoreExtras() {
|
||||
return moreExtras;
|
||||
}
|
||||
|
||||
public final void addExtra(String key, Object value) {
|
||||
moreExtras.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a scan for all known barcode types with the default camera.
|
||||
*
|
||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
||||
* if a prompt was needed, or null otherwise.
|
||||
*/
|
||||
public final AlertDialog initiateScan() {
|
||||
return initiateScan(ALL_CODE_TYPES, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a scan for all known barcode types with the specified camera.
|
||||
*
|
||||
* @param cameraId camera ID of the camera to use. A negative value means "no preference".
|
||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
||||
* if a prompt was needed, or null otherwise.
|
||||
*/
|
||||
public final AlertDialog initiateScan(int cameraId) {
|
||||
return initiateScan(ALL_CODE_TYPES, cameraId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a scan, using the default camera, only for a certain set of barcode types, given as strings corresponding
|
||||
* to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants
|
||||
* like {@link #PRODUCT_CODE_TYPES} for example.
|
||||
*
|
||||
* @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
|
||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
||||
* if a prompt was needed, or null otherwise.
|
||||
*/
|
||||
public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats) {
|
||||
return initiateScan(desiredBarcodeFormats, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a scan, using the specified camera, only for a certain set of barcode types, given as strings corresponding
|
||||
* to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants
|
||||
* like {@link #PRODUCT_CODE_TYPES} for example.
|
||||
*
|
||||
* @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
|
||||
* @param cameraId camera ID of the camera to use. A negative value means "no preference".
|
||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
||||
* if a prompt was needed, or null otherwise
|
||||
*/
|
||||
public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats, int cameraId) {
|
||||
Intent intentScan = new Intent(BS_PACKAGE + ".SCAN");
|
||||
intentScan.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
|
||||
// check which types of codes to scan for
|
||||
if (desiredBarcodeFormats != null) {
|
||||
// set the desired barcode types
|
||||
StringBuilder joinedByComma = new StringBuilder();
|
||||
for (String format : desiredBarcodeFormats) {
|
||||
if (joinedByComma.length() > 0) {
|
||||
joinedByComma.append(',');
|
||||
}
|
||||
joinedByComma.append(format);
|
||||
}
|
||||
intentScan.putExtra("SCAN_FORMATS", joinedByComma.toString());
|
||||
}
|
||||
|
||||
// check requested camera ID
|
||||
if (cameraId >= 0) {
|
||||
intentScan.putExtra("SCAN_CAMERA_ID", cameraId);
|
||||
}
|
||||
|
||||
String targetAppPackage = findTargetAppPackage(intentScan);
|
||||
if (targetAppPackage == null) {
|
||||
return showDownloadDialog();
|
||||
}
|
||||
intentScan.setPackage(targetAppPackage);
|
||||
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
|
||||
attachMoreExtras(intentScan);
|
||||
startActivityForResult(intentScan, REQUEST_CODE);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start an activity. This method is defined to allow different methods of activity starting for
|
||||
* newer versions of Android and for compatibility library.
|
||||
*
|
||||
* @param intent Intent to start.
|
||||
* @param code Request code for the activity
|
||||
* @see android.app.Activity#startActivityForResult(Intent, int)
|
||||
* @see android.app.Fragment#startActivityForResult(Intent, int)
|
||||
*/
|
||||
protected void startActivityForResult(Intent intent, int code) {
|
||||
if (fragment == null) {
|
||||
activity.startActivityForResult(intent, code);
|
||||
} else {
|
||||
fragment.startActivityForResult(intent, code);
|
||||
}
|
||||
}
|
||||
|
||||
private String findTargetAppPackage(Intent intent) {
|
||||
PackageManager pm = activity.getPackageManager();
|
||||
List<ResolveInfo> availableApps = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
if (availableApps != null) {
|
||||
for (String targetApp : targetApplications) {
|
||||
if (contains(availableApps, targetApp)) {
|
||||
return targetApp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean contains(Iterable<ResolveInfo> availableApps, String targetApp) {
|
||||
for (ResolveInfo availableApp : availableApps) {
|
||||
String packageName = availableApp.activityInfo.packageName;
|
||||
if (targetApp.equals(packageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private AlertDialog showDownloadDialog() {
|
||||
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity);
|
||||
downloadDialog.setTitle(title);
|
||||
downloadDialog.setMessage(message);
|
||||
downloadDialog.setPositiveButton(buttonYes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
String packageName;
|
||||
if (targetApplications.contains(BS_PACKAGE)) {
|
||||
// Prefer to suggest download of BS if it's anywhere in the list
|
||||
packageName = BS_PACKAGE;
|
||||
} else {
|
||||
// Otherwise, first option:
|
||||
packageName = targetApplications.get(0);
|
||||
}
|
||||
Uri uri = Uri.parse("market://details?id=" + packageName);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
try {
|
||||
if (fragment == null) {
|
||||
activity.startActivity(intent);
|
||||
} else {
|
||||
fragment.startActivity(intent);
|
||||
}
|
||||
} catch (ActivityNotFoundException anfe) {
|
||||
// Hmm, market is not installed
|
||||
Log.w(TAG, "Google Play is not installed; cannot install " + packageName);
|
||||
}
|
||||
}
|
||||
});
|
||||
downloadDialog.setNegativeButton(buttonNo, null);
|
||||
downloadDialog.setCancelable(true);
|
||||
return downloadDialog.show();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>Call this from your {@link Activity}'s
|
||||
* {@link Activity#onActivityResult(int, int, Intent)} method.</p>
|
||||
*
|
||||
* @param requestCode request code from {@code onActivityResult()}
|
||||
* @param resultCode result code from {@code onActivityResult()}
|
||||
* @param intent {@link Intent} from {@code onActivityResult()}
|
||||
* @return null if the event handled here was not related to this class, or
|
||||
* else an {@link IntentResult} containing the result of the scan. If the user cancelled scanning,
|
||||
* the fields will be null.
|
||||
*/
|
||||
public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
if (requestCode == REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
String contents = intent.getStringExtra("SCAN_RESULT");
|
||||
String formatName = intent.getStringExtra("SCAN_RESULT_FORMAT");
|
||||
byte[] rawBytes = intent.getByteArrayExtra("SCAN_RESULT_BYTES");
|
||||
int intentOrientation = intent.getIntExtra("SCAN_RESULT_ORIENTATION", Integer.MIN_VALUE);
|
||||
Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation;
|
||||
String errorCorrectionLevel = intent.getStringExtra("SCAN_RESULT_ERROR_CORRECTION_LEVEL");
|
||||
return new IntentResult(contents,
|
||||
formatName,
|
||||
rawBytes,
|
||||
orientation,
|
||||
errorCorrectionLevel);
|
||||
}
|
||||
return new IntentResult();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defaults to type "TEXT_TYPE".
|
||||
*
|
||||
* @param text the text string to encode as a barcode
|
||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
||||
* if a prompt was needed, or null otherwise
|
||||
* @see #shareText(CharSequence, CharSequence)
|
||||
*/
|
||||
public final AlertDialog shareText(CharSequence text) {
|
||||
return shareText(text, "TEXT_TYPE");
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares the given text by encoding it as a barcode, such that another user can
|
||||
* scan the text off the screen of the device.
|
||||
*
|
||||
* @param text the text string to encode as a barcode
|
||||
* @param type type of data to encode. See {@code com.google.zxing.client.android.Contents.Type} constants.
|
||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
||||
* if a prompt was needed, or null otherwise
|
||||
*/
|
||||
public final AlertDialog shareText(CharSequence text, CharSequence type) {
|
||||
Intent intent = new Intent();
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
intent.setAction(BS_PACKAGE + ".ENCODE");
|
||||
intent.putExtra("ENCODE_TYPE", type);
|
||||
intent.putExtra("ENCODE_DATA", text);
|
||||
String targetAppPackage = findTargetAppPackage(intent);
|
||||
if (targetAppPackage == null) {
|
||||
return showDownloadDialog();
|
||||
}
|
||||
intent.setPackage(targetAppPackage);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
|
||||
attachMoreExtras(intent);
|
||||
if (fragment == null) {
|
||||
activity.startActivity(intent);
|
||||
} else {
|
||||
fragment.startActivity(intent);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<String> list(String... values) {
|
||||
return Collections.unmodifiableList(Arrays.asList(values));
|
||||
}
|
||||
|
||||
private void attachMoreExtras(Intent intent) {
|
||||
for (Map.Entry<String,Object> entry : moreExtras.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
// Kind of hacky
|
||||
if (value instanceof Integer) {
|
||||
intent.putExtra(key, (Integer) value);
|
||||
} else if (value instanceof Long) {
|
||||
intent.putExtra(key, (Long) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
intent.putExtra(key, (Boolean) value);
|
||||
} else if (value instanceof Double) {
|
||||
intent.putExtra(key, (Double) value);
|
||||
} else if (value instanceof Float) {
|
||||
intent.putExtra(key, (Float) value);
|
||||
} else if (value instanceof Bundle) {
|
||||
intent.putExtra(key, (Bundle) value);
|
||||
} else {
|
||||
intent.putExtra(key, value.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright 2009 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.integration.android;
|
||||
|
||||
/**
|
||||
* <p>Encapsulates the result of a barcode scan invoked through {@link IntentIntegrator}.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
public final class IntentResult {
|
||||
|
||||
private final String contents;
|
||||
private final String formatName;
|
||||
private final byte[] rawBytes;
|
||||
private final Integer orientation;
|
||||
private final String errorCorrectionLevel;
|
||||
|
||||
IntentResult() {
|
||||
this(null, null, null, null, null);
|
||||
}
|
||||
|
||||
IntentResult(String contents,
|
||||
String formatName,
|
||||
byte[] rawBytes,
|
||||
Integer orientation,
|
||||
String errorCorrectionLevel) {
|
||||
this.contents = contents;
|
||||
this.formatName = formatName;
|
||||
this.rawBytes = rawBytes;
|
||||
this.orientation = orientation;
|
||||
this.errorCorrectionLevel = errorCorrectionLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return raw content of barcode
|
||||
*/
|
||||
public String getContents() {
|
||||
return contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return name of format, like "QR_CODE", "UPC_A". See {@code BarcodeFormat} for more format names.
|
||||
*/
|
||||
public String getFormatName() {
|
||||
return formatName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return raw bytes of the barcode content, if applicable, or null otherwise
|
||||
*/
|
||||
public byte[] getRawBytes() {
|
||||
return rawBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return rotation of the image, in degrees, which resulted in a successful scan. May be null.
|
||||
*/
|
||||
public Integer getOrientation() {
|
||||
return orientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return name of the error correction level used in the barcode, if applicable
|
||||
*/
|
||||
public String getErrorCorrectionLevel() {
|
||||
return errorCorrectionLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder dialogText = new StringBuilder(100);
|
||||
dialogText.append("Format: ").append(formatName).append('\n');
|
||||
dialogText.append("Contents: ").append(contents).append('\n');
|
||||
int rawBytesLength = rawBytes == null ? 0 : rawBytes.length;
|
||||
dialogText.append("Raw bytes: (").append(rawBytesLength).append(" bytes)\n");
|
||||
dialogText.append("Orientation: ").append(orientation).append('\n');
|
||||
dialogText.append("EC level: ").append(errorCorrectionLevel).append('\n');
|
||||
return dialogText.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package org.torproject.android;
|
||||
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.torproject.android.service.Prefs;
|
||||
import org.torproject.android.service.TorService;
|
||||
import org.torproject.android.service.TorServiceConstants;
|
||||
import org.torproject.android.vpn.VPNEnableActivity;
|
||||
|
||||
public class OnBootReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Prefs.setContext(context);
|
||||
if (Prefs.startOnBoot())
|
||||
{
|
||||
|
||||
if (Prefs.useVpn())
|
||||
startVpnService(context); //VPN will start Tor once it is done
|
||||
else
|
||||
startService(TorServiceConstants.ACTION_START, context);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void startVpnService (final Context context)
|
||||
{
|
||||
Intent intent = new Intent(context,VPNEnableActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
private void startService (String action, Context context)
|
||||
{
|
||||
|
||||
Intent torService = new Intent(context, TorService.class);
|
||||
torService.setAction(action);
|
||||
context.startService(torService);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
|
||||
package org.torproject.android;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.util.Log;
|
||||
|
||||
import org.torproject.android.service.OrbotConstants;
|
||||
import org.torproject.android.service.Prefs;
|
||||
import org.torproject.android.service.TorServiceConstants;
|
||||
|
||||
import org.torproject.android.settings.Languages;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Locale;
|
||||
|
||||
public class OrbotApp extends Application implements OrbotConstants
|
||||
{
|
||||
|
||||
private Locale locale;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Prefs.setContext(this);
|
||||
|
||||
Languages.setup(OrbotMainActivity.class, R.string.menu_settings);
|
||||
Languages.setLanguage(this, Prefs.getDefaultLocale(), true);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
Log.i(TAG, "onConfigurationChanged " + newConfig.locale.getLanguage());
|
||||
Languages.setLanguage(this, Prefs.getDefaultLocale(), true);
|
||||
}
|
||||
|
||||
public static void forceChangeLanguage(Activity activity) {
|
||||
Intent intent = activity.getIntent();
|
||||
if (intent == null) // when launched as LAUNCHER
|
||||
intent = new Intent(activity, OrbotMainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
activity.finish();
|
||||
activity.overridePendingTransition(0, 0);
|
||||
activity.startActivity(intent);
|
||||
activity.overridePendingTransition(0, 0);
|
||||
}
|
||||
|
||||
public static Languages getLanguages(Activity activity) {
|
||||
return Languages.get(activity);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
package org.torproject.android.settings;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.Intent;
|
||||
import android.content.res.AssetManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class Languages {
|
||||
public static final String TAG = "Languages";
|
||||
|
||||
public static final Locale defaultLocale;
|
||||
public static final Locale TIBETAN = new Locale("bo");
|
||||
static final Locale localesToTest[] = {
|
||||
Locale.ENGLISH, Locale.FRENCH, Locale.GERMAN,
|
||||
Locale.ITALIAN, Locale.JAPANESE, Locale.KOREAN,
|
||||
Locale.TRADITIONAL_CHINESE, Locale.SIMPLIFIED_CHINESE,
|
||||
TIBETAN, new Locale("af"), new Locale("am"),
|
||||
new Locale("ar"), new Locale("az"), new Locale("bg"),
|
||||
new Locale("bn"), new Locale("ca"), new Locale("cs"),
|
||||
new Locale("da"), new Locale("el"), new Locale("es"),
|
||||
new Locale("et"), new Locale("eu"), new Locale("fa"),
|
||||
new Locale("fi"), new Locale("gl"), new Locale("hi"),
|
||||
new Locale("hr"), new Locale("hu"), new Locale("hy"),
|
||||
new Locale("in"), new Locale("hy"), new Locale("in"),
|
||||
new Locale("is"), new Locale("it"), new Locale("iw"),
|
||||
new Locale("ka"), new Locale("kk"), new Locale("km"),
|
||||
new Locale("kn"), new Locale("ky"), new Locale("lo"),
|
||||
new Locale("lt"), new Locale("lv"), new Locale("mk"),
|
||||
new Locale("ml"), new Locale("mn"), new Locale("mr"),
|
||||
new Locale("ms"), new Locale("my"), new Locale("nb"),
|
||||
new Locale("ne"), new Locale("nl"), new Locale("pl"),
|
||||
new Locale("pt"), new Locale("rm"), new Locale("ro"),
|
||||
new Locale("ru"), new Locale("si"), new Locale("sk"),
|
||||
new Locale("sl"), new Locale("sn"), new Locale("sr"),
|
||||
new Locale("sv"), new Locale("sw"), new Locale("ta"),
|
||||
new Locale("te"), new Locale("th"), new Locale("tl"),
|
||||
new Locale("tr"), new Locale("uk"), new Locale("ur"),
|
||||
new Locale("uz"), new Locale("vi"), new Locale("zu"),
|
||||
};
|
||||
|
||||
private static final String USE_SYSTEM_DEFAULT = "";
|
||||
private static final String defaultString = "Use System Default";
|
||||
|
||||
private static Locale locale;
|
||||
private static Languages singleton;
|
||||
private static Class<?> clazz;
|
||||
private static int resId;
|
||||
private static Map<String, String> tmpMap = new TreeMap<String, String>();
|
||||
private static Map<String, String> nameMap;
|
||||
|
||||
static {
|
||||
defaultLocale = Locale.getDefault();
|
||||
}
|
||||
|
||||
private Languages(Activity activity) {
|
||||
AssetManager assets = activity.getAssets();
|
||||
Configuration config = activity.getResources().getConfiguration();
|
||||
// Resources() requires DisplayMetrics, but they are only needed for drawables
|
||||
DisplayMetrics ignored = new DisplayMetrics();
|
||||
activity.getWindowManager().getDefaultDisplay().getMetrics(ignored);
|
||||
Resources resources;
|
||||
Set<Locale> localeSet = new LinkedHashSet<Locale>();
|
||||
for (Locale locale : localesToTest) {
|
||||
config.locale = locale;
|
||||
resources = new Resources(assets, ignored, config);
|
||||
if (!TextUtils.equals(defaultString, resources.getString(resId))
|
||||
|| locale.equals(Locale.ENGLISH))
|
||||
localeSet.add(locale);
|
||||
}
|
||||
for (Locale locale : localeSet) {
|
||||
if (locale.equals(TIBETAN)) {
|
||||
// include English name for devices without Tibetan font support
|
||||
tmpMap.put(TIBETAN.getLanguage(), "Tibetan བོད་སྐད།"); // Tibetan
|
||||
} else if (locale.equals(Locale.SIMPLIFIED_CHINESE)) {
|
||||
tmpMap.put(Locale.SIMPLIFIED_CHINESE.toString(), "中文 (中国)"); // Chinese (China)
|
||||
} else if (locale.equals(Locale.TRADITIONAL_CHINESE)) {
|
||||
tmpMap.put(Locale.TRADITIONAL_CHINESE.toString(), "中文 (台灣)"); // Chinese (Taiwan)
|
||||
} else {
|
||||
tmpMap.put(locale.getLanguage(), locale.getDisplayLanguage(locale));
|
||||
}
|
||||
}
|
||||
|
||||
/* USE_SYSTEM_DEFAULT is a fake one for displaying in a chooser menu. */
|
||||
localeSet.add(null);
|
||||
tmpMap.put(USE_SYSTEM_DEFAULT, activity.getString(resId));
|
||||
nameMap = Collections.unmodifiableMap(tmpMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance of {@link Languages} to work with, providing the
|
||||
* {@link Activity} that is will be working as part of, as well as the
|
||||
* {@code resId} that has the exact string "Use System Default",
|
||||
* i.e. {@code R.string.use_system_default}.
|
||||
* <p/>
|
||||
* That string resource {@code resId} is also used to find the supported
|
||||
* translations: if an included translation has a translated string that
|
||||
* matches that {@code resId}, then that language will be included as a
|
||||
* supported language.
|
||||
*
|
||||
* @param clazz the {@link Class} of the default {@code Activity},
|
||||
* usually the main {@code Activity} from where the
|
||||
* Settings is launched from.
|
||||
* @param resId the string resource ID to for the string "Use System Default",
|
||||
* e.g. {@code R.string.use_system_default}
|
||||
* @return
|
||||
*/
|
||||
public static void setup(Class<?> clazz, int resId) {
|
||||
if (Languages.clazz == null) {
|
||||
Languages.clazz = clazz;
|
||||
Languages.resId = resId;
|
||||
} else {
|
||||
throw new RuntimeException("Languages singleton was already initialized, duplicate call to Languages.setup()!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton to work with.
|
||||
*
|
||||
* @param activity the {@link Activity} this is working as part of
|
||||
* @return
|
||||
*/
|
||||
public static Languages get(Activity activity) {
|
||||
if (singleton == null) {
|
||||
singleton = new Languages(activity);
|
||||
}
|
||||
return singleton;
|
||||
}
|
||||
|
||||
//@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
|
||||
@SuppressLint("NewApi")
|
||||
public static void setLanguage(final ContextWrapper contextWrapper, String language, boolean refresh) {
|
||||
if (locale != null && TextUtils.equals(locale.getLanguage(), language) && (!refresh)) {
|
||||
return; // already configured
|
||||
} else if (language == null || language == USE_SYSTEM_DEFAULT) {
|
||||
locale = defaultLocale;
|
||||
} else {
|
||||
/* handle locales with the country in it, i.e. zh_CN, zh_TW, etc */
|
||||
String localeSplit[] = language.split("_");
|
||||
if (localeSplit.length > 1) {
|
||||
locale = new Locale(localeSplit[0], localeSplit[1]);
|
||||
} else {
|
||||
locale = new Locale(language);
|
||||
}
|
||||
}
|
||||
|
||||
final Resources resources = contextWrapper.getBaseContext().getResources();
|
||||
Configuration config = resources.getConfiguration();
|
||||
if (Build.VERSION.SDK_INT >= 17) {
|
||||
config.setLocale(locale);
|
||||
} else {
|
||||
config.locale = locale;
|
||||
}
|
||||
resources.updateConfiguration(config, resources.getDisplayMetrics());
|
||||
Locale.setDefault(locale);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Force reload the {@link Activity to make language changes take effect.}
|
||||
*
|
||||
* @param activity the {@code Activity} to force reload
|
||||
*/
|
||||
public static void forceChangeLanguage(Activity activity) {
|
||||
Intent intent = activity.getIntent();
|
||||
if (intent == null) // when launched as LAUNCHER
|
||||
return;
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
activity.finish();
|
||||
activity.overridePendingTransition(0, 0);
|
||||
activity.startActivity(intent);
|
||||
activity.overridePendingTransition(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the language based on the locale.
|
||||
*
|
||||
* @param locale
|
||||
* @return
|
||||
*/
|
||||
public String getName(String locale) {
|
||||
String ret = nameMap.get(locale);
|
||||
// if no match, try to return a more general name (i.e. English for
|
||||
// en_IN)
|
||||
if (ret == null && locale.contains("_"))
|
||||
ret = nameMap.get(locale.split("_")[0]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of the names of all the supported languages, sorted to
|
||||
* match what is returned by {@link Languages#getSupportedLocales()}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String[] getAllNames() {
|
||||
return nameMap.values().toArray(new String[nameMap.size()]);
|
||||
}
|
||||
|
||||
public int getPosition(Locale locale) {
|
||||
String localeName = locale.getLanguage();
|
||||
int i = 0;
|
||||
for (String key : nameMap.keySet())
|
||||
if (TextUtils.equals(key, localeName))
|
||||
return i;
|
||||
else
|
||||
i++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sorted list of supported locales.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String[] getSupportedLocales() {
|
||||
Set<String> keys = nameMap.keySet();
|
||||
return keys.toArray(new String[keys.size()]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/* Copyright (c) 2009, Nathan Freitas, Orbot / The Guardian Project - http://openideals.com/guardian */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
package org.torproject.android.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.Preference.OnPreferenceChangeListener;
|
||||
import android.preference.Preference.OnPreferenceClickListener;
|
||||
import android.preference.PreferenceActivity;
|
||||
|
||||
import org.torproject.android.OrbotApp;
|
||||
import org.torproject.android.R;
|
||||
import org.torproject.android.ui.AppManager;
|
||||
import org.torproject.android.service.TorServiceUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
public class SettingsPreferences
|
||||
extends PreferenceActivity implements OnPreferenceClickListener {
|
||||
private static final String TAG = "SettingsPreferences";
|
||||
|
||||
private CheckBoxPreference prefCBTransProxy = null;
|
||||
private CheckBoxPreference prefcBTransProxyAll = null;
|
||||
private Preference prefTransProxyFlush = null;
|
||||
|
||||
private Preference prefTransProxyApps = null;
|
||||
private CheckBoxPreference prefHiddenServices = null;
|
||||
private EditTextPreference prefHiddenServicesPorts;
|
||||
private EditTextPreference prefHiddenServicesHostname;
|
||||
private CheckBoxPreference prefRequestRoot = null;
|
||||
private ListPreference prefLocale = null;
|
||||
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
getPreferenceManager().setSharedPreferencesMode(Context.MODE_MULTI_PROCESS);
|
||||
SharedPreferences prefs = TorServiceUtils.getSharedPrefs(getApplicationContext());
|
||||
|
||||
prefRequestRoot = (CheckBoxPreference) findPreference("has_root");
|
||||
prefRequestRoot.setOnPreferenceClickListener(this);
|
||||
|
||||
prefLocale = (ListPreference) findPreference("pref_default_locale");
|
||||
prefLocale.setOnPreferenceClickListener(this);
|
||||
Languages languages = Languages.get(this);
|
||||
prefLocale.setEntries(languages.getAllNames());
|
||||
prefLocale.setEntryValues(languages.getSupportedLocales());
|
||||
prefLocale.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
String language = (String) newValue;
|
||||
if (preference == prefLocale) {
|
||||
SharedPreferences settings = TorServiceUtils
|
||||
.getSharedPrefs(getApplicationContext());
|
||||
|
||||
String lang = settings.getString("pref_default_locale",
|
||||
Locale.getDefault().getLanguage());
|
||||
OrbotApp app = (OrbotApp) getApplication();
|
||||
Languages.setLanguage(app, language, true);
|
||||
lang = settings.getString("pref_default_locale",
|
||||
Locale.getDefault().getLanguage());
|
||||
OrbotApp.forceChangeLanguage(SettingsPreferences.this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
prefCBTransProxy = (CheckBoxPreference) findPreference("pref_transparent");
|
||||
prefcBTransProxyAll = (CheckBoxPreference) findPreference("pref_transparent_all");
|
||||
|
||||
prefTransProxyFlush = (Preference) findPreference("pref_transproxy_flush");
|
||||
prefTransProxyFlush.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference arg0) {
|
||||
|
||||
Intent data = new Intent();
|
||||
data.putExtra("transproxywipe", true);
|
||||
setResult(RESULT_OK, data);
|
||||
|
||||
finish();
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
prefTransProxyApps = findPreference("pref_transparent_app_list");
|
||||
prefTransProxyApps.setOnPreferenceClickListener(this);
|
||||
prefCBTransProxy.setOnPreferenceClickListener(this);
|
||||
prefcBTransProxyAll.setOnPreferenceClickListener(this);
|
||||
prefHiddenServices = (CheckBoxPreference) findPreference("pref_hs_enable");
|
||||
prefHiddenServices.setOnPreferenceClickListener(this);
|
||||
prefHiddenServicesHostname = (EditTextPreference) findPreference("pref_hs_hostname");
|
||||
|
||||
|
||||
prefCBTransProxy.setEnabled(prefRequestRoot.isChecked());
|
||||
|
||||
prefcBTransProxyAll.setEnabled(prefCBTransProxy.isChecked());
|
||||
|
||||
if (prefCBTransProxy.isChecked())
|
||||
prefTransProxyApps.setEnabled((!prefcBTransProxyAll.isChecked()));
|
||||
|
||||
prefHiddenServicesPorts = (EditTextPreference) findPreference("pref_hs_ports");
|
||||
prefHiddenServicesHostname.setEnabled(prefHiddenServices.isChecked());
|
||||
prefHiddenServicesPorts.setEnabled(prefHiddenServices.isChecked());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
{
|
||||
prefTransProxyApps.setEnabled(true);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
|
||||
setResult(RESULT_OK);
|
||||
|
||||
if (preference == prefRequestRoot)
|
||||
{
|
||||
if (prefRequestRoot.isChecked())
|
||||
{
|
||||
|
||||
prefCBTransProxy.setEnabled(true);
|
||||
|
||||
}
|
||||
}
|
||||
else if (preference == prefTransProxyApps)
|
||||
{
|
||||
startActivity(new Intent(this, AppManager.class));
|
||||
|
||||
}
|
||||
else if (preference == prefHiddenServices)
|
||||
{
|
||||
prefHiddenServicesPorts.setEnabled(prefHiddenServices.isChecked());
|
||||
prefHiddenServicesHostname.setEnabled(prefHiddenServices.isChecked());
|
||||
}
|
||||
else
|
||||
{
|
||||
prefcBTransProxyAll.setEnabled(prefCBTransProxy.isChecked());
|
||||
prefTransProxyApps.setEnabled(prefCBTransProxy.isChecked() && (!prefcBTransProxyAll.isChecked()));
|
||||
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,389 @@
|
|||
/* Copyright (c) 2009, Nathan Freitas, Orbot / The Guardian Project - http://openideals.com/guardian */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
package org.torproject.android.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.torproject.android.service.OrbotConstants;
|
||||
import org.torproject.android.R;
|
||||
import org.torproject.android.service.TorServiceUtils;
|
||||
import org.torproject.android.service.TorifiedApp;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class AppManager extends Activity implements OnCheckedChangeListener, OnClickListener, OrbotConstants {
|
||||
|
||||
private ListView listApps;
|
||||
private final static String TAG = "Orbot";
|
||||
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
this.setContentView(R.layout.layout_apps);
|
||||
|
||||
Button buttonSelectAll, buttonSelectNone, buttonInvert;
|
||||
|
||||
buttonSelectAll = (Button) findViewById(R.id.button_proxy_all);
|
||||
buttonSelectNone = (Button) findViewById(R.id.button_proxy_none);
|
||||
buttonInvert = (Button) findViewById(R.id.button_invert_selection);
|
||||
|
||||
buttonSelectAll.setOnClickListener(new OnAutoClickListener(0));
|
||||
buttonSelectNone.setOnClickListener(new OnAutoClickListener(1));
|
||||
buttonInvert.setOnClickListener(new OnAutoClickListener(2));
|
||||
}
|
||||
|
||||
class OnAutoClickListener implements Button.OnClickListener {
|
||||
private int status;
|
||||
public OnAutoClickListener(int status){
|
||||
this.status = status;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onClick(View button){
|
||||
ListView listView;
|
||||
ViewGroup viewGroup;
|
||||
View parentView, currentView;
|
||||
ArrayAdapter<TorifiedApp> adapter;
|
||||
TorifiedApp app;
|
||||
CheckBox box;
|
||||
float buttonId;
|
||||
boolean[] isSelected;
|
||||
int posI, selectedI, lvSz;
|
||||
|
||||
buttonId = button.getId();
|
||||
listView = (ListView) findViewById(R.id.applistview);
|
||||
lvSz = listView.getCount();
|
||||
isSelected = new boolean[lvSz];
|
||||
|
||||
selectedI = -1;
|
||||
|
||||
if (this.status == 0){
|
||||
Log.d(TAG, "Proxifying ALL");
|
||||
}else if (this.status == 1){
|
||||
Log.d(TAG, "Proxifying NONE");
|
||||
}else {
|
||||
Log.d(TAG, "Proxifying invert");
|
||||
}
|
||||
|
||||
Context context = getApplicationContext();
|
||||
SharedPreferences prefs = TorServiceUtils.getSharedPrefs(context);
|
||||
ArrayList<TorifiedApp> apps = getApps(context, prefs);
|
||||
parentView = (View) findViewById(R.id.applistview);
|
||||
viewGroup = (ViewGroup) listView;
|
||||
|
||||
adapter = (ArrayAdapter<TorifiedApp>) listApps.getAdapter();
|
||||
if (adapter == null){
|
||||
Log.w(TAG, "List adapter is null. Getting apps.");
|
||||
loadApps(prefs);
|
||||
adapter = (ArrayAdapter<TorifiedApp>) listApps.getAdapter();
|
||||
}
|
||||
|
||||
for (int i = 0 ; i < adapter.getCount(); ++i){
|
||||
app = (TorifiedApp) adapter.getItem(i);
|
||||
currentView = adapter.getView(i, parentView, viewGroup);
|
||||
box = (CheckBox) currentView.findViewById(R.id.itemcheck);
|
||||
if (this.status == 0){
|
||||
if (!box.isChecked())
|
||||
box.performClick();
|
||||
}else if (this.status == 1){
|
||||
if (box.isChecked())
|
||||
box.performClick();
|
||||
}else {
|
||||
box.performClick();
|
||||
}
|
||||
}
|
||||
saveAppSettings(context);
|
||||
loadApps(prefs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
listApps = (ListView)findViewById(R.id.applistview);
|
||||
|
||||
Button btnSave = (Button)findViewById(R.id.btnsave);
|
||||
btnSave.setOnClickListener(new OnClickListener()
|
||||
{
|
||||
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
mPrefs = TorServiceUtils.getSharedPrefs(getApplicationContext());
|
||||
loadApps(mPrefs);
|
||||
}
|
||||
|
||||
SharedPreferences mPrefs = null;
|
||||
ArrayList<TorifiedApp> mApps = null;
|
||||
|
||||
private void loadApps (SharedPreferences prefs)
|
||||
{
|
||||
|
||||
mApps = getApps(getApplicationContext(), prefs);
|
||||
|
||||
/*
|
||||
Arrays.sort(apps, new Comparator<TorifiedApp>() {
|
||||
public int compare(TorifiedApp o1, TorifiedApp o2) {
|
||||
if (o1.isTorified() == o2.isTorified()) return o1.getName().compareTo(o2.getName());
|
||||
if (o1.isTorified()) return -1;
|
||||
return 1;
|
||||
}
|
||||
});*/
|
||||
|
||||
final LayoutInflater inflater = getLayoutInflater();
|
||||
|
||||
ListAdapter adapter = new ArrayAdapter<TorifiedApp>(this, R.layout.layout_apps_item, R.id.itemtext,mApps) {
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
ListEntry entry;
|
||||
if (convertView == null) {
|
||||
// Inflate a new view
|
||||
convertView = inflater.inflate(R.layout.layout_apps_item, parent, false);
|
||||
entry = new ListEntry();
|
||||
entry.icon = (ImageView) convertView.findViewById(R.id.itemicon);
|
||||
entry.box = (CheckBox) convertView.findViewById(R.id.itemcheck);
|
||||
entry.text = (TextView) convertView.findViewById(R.id.itemtext);
|
||||
|
||||
entry.text.setOnClickListener(AppManager.this);
|
||||
entry.text.setOnClickListener(AppManager.this);
|
||||
|
||||
convertView.setTag(entry);
|
||||
|
||||
entry.box.setOnCheckedChangeListener(AppManager.this);
|
||||
} else {
|
||||
// Convert an existing view
|
||||
entry = (ListEntry) convertView.getTag();
|
||||
}
|
||||
|
||||
|
||||
final TorifiedApp app = mApps.get(position);
|
||||
|
||||
if (app.getIcon() != null)
|
||||
entry.icon.setImageDrawable(app.getIcon());
|
||||
else
|
||||
entry.icon.setVisibility(View.GONE);
|
||||
|
||||
entry.text.setText(app.getName());
|
||||
|
||||
final CheckBox box = entry.box;
|
||||
box.setTag(app);
|
||||
box.setChecked(app.isTorified());
|
||||
|
||||
entry.text.setTag(box);
|
||||
entry.icon.setTag(box);
|
||||
|
||||
return convertView;
|
||||
}
|
||||
};
|
||||
|
||||
listApps.setAdapter(adapter);
|
||||
|
||||
}
|
||||
|
||||
private static class ListEntry {
|
||||
private CheckBox box;
|
||||
private TextView text;
|
||||
private ImageView icon;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see android.app.Activity#onStop()
|
||||
*/
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static ArrayList<TorifiedApp> getApps (Context context, SharedPreferences prefs)
|
||||
{
|
||||
|
||||
String tordAppString = prefs.getString(PREFS_KEY_TORIFIED, "");
|
||||
String[] tordApps;
|
||||
|
||||
StringTokenizer st = new StringTokenizer(tordAppString,"|");
|
||||
tordApps = new String[st.countTokens()];
|
||||
int tordIdx = 0;
|
||||
while (st.hasMoreTokens())
|
||||
{
|
||||
tordApps[tordIdx++] = st.nextToken();
|
||||
}
|
||||
|
||||
Arrays.sort(tordApps);
|
||||
|
||||
//else load the apps up
|
||||
PackageManager pMgr = context.getPackageManager();
|
||||
|
||||
List<ApplicationInfo> lAppInfo = pMgr.getInstalledApplications(0);
|
||||
|
||||
Iterator<ApplicationInfo> itAppInfo = lAppInfo.iterator();
|
||||
|
||||
ArrayList<TorifiedApp> apps = new ArrayList<TorifiedApp>();
|
||||
|
||||
ApplicationInfo aInfo = null;
|
||||
|
||||
int appIdx = 0;
|
||||
TorifiedApp app = null;
|
||||
|
||||
while (itAppInfo.hasNext())
|
||||
{
|
||||
aInfo = itAppInfo.next();
|
||||
|
||||
app = new TorifiedApp();
|
||||
|
||||
try {
|
||||
PackageInfo pInfo = pMgr.getPackageInfo(aInfo.packageName, PackageManager.GET_PERMISSIONS);
|
||||
|
||||
if (pInfo != null && pInfo.requestedPermissions != null)
|
||||
{
|
||||
for (String permInfo:pInfo.requestedPermissions)
|
||||
{
|
||||
if (permInfo.equals("android.permission.INTERNET"))
|
||||
{
|
||||
app.setUsesInternet(true);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 1)
|
||||
{
|
||||
//System app
|
||||
app.setUsesInternet(true);
|
||||
}
|
||||
|
||||
|
||||
if (!app.usesInternet())
|
||||
continue;
|
||||
else
|
||||
{
|
||||
apps.add(app);
|
||||
}
|
||||
|
||||
|
||||
app.setEnabled(aInfo.enabled);
|
||||
app.setUid(aInfo.uid);
|
||||
app.setUsername(pMgr.getNameForUid(app.getUid()));
|
||||
app.setProcname(aInfo.processName);
|
||||
app.setPackageName(aInfo.packageName);
|
||||
|
||||
try
|
||||
{
|
||||
app.setName(pMgr.getApplicationLabel(aInfo).toString());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
app.setName(aInfo.packageName);
|
||||
}
|
||||
|
||||
|
||||
//app.setIcon(pMgr.getApplicationIcon(aInfo));
|
||||
|
||||
// check if this application is allowed
|
||||
if (Arrays.binarySearch(tordApps, app.getUsername()) >= 0) {
|
||||
app.setTorified(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
app.setTorified(false);
|
||||
}
|
||||
|
||||
appIdx++;
|
||||
}
|
||||
|
||||
Collections.sort(apps);
|
||||
|
||||
return apps;
|
||||
}
|
||||
|
||||
|
||||
public void saveAppSettings (Context context)
|
||||
{
|
||||
|
||||
StringBuilder tordApps = new StringBuilder();
|
||||
|
||||
for (TorifiedApp tApp:mApps)
|
||||
{
|
||||
if (tApp.isTorified())
|
||||
{
|
||||
tordApps.append(tApp.getUsername());
|
||||
tordApps.append("|");
|
||||
}
|
||||
}
|
||||
|
||||
Editor edit = mPrefs.edit();
|
||||
edit.putString(PREFS_KEY_TORIFIED, tordApps.toString());
|
||||
edit.commit();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called an application is check/unchecked
|
||||
*/
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
final TorifiedApp app = (TorifiedApp) buttonView.getTag();
|
||||
if (app != null) {
|
||||
app.setTorified(isChecked);
|
||||
}
|
||||
|
||||
saveAppSettings(this);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void onClick(View v) {
|
||||
|
||||
CheckBox cbox = (CheckBox)v.getTag();
|
||||
|
||||
final TorifiedApp app = (TorifiedApp)cbox.getTag();
|
||||
if (app != null) {
|
||||
app.setTorified(!app.isTorified());
|
||||
cbox.setChecked(app.isTorified());
|
||||
}
|
||||
|
||||
saveAppSettings(this);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.torproject.android.ui;
|
||||
|
||||
public class BridgeSetupActivity {
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package org.torproject.android.ui;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
public class ImageProgressView extends ImageView
|
||||
{
|
||||
|
||||
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
private float progress = 0f; // 0 to 1
|
||||
|
||||
private RectF circle;
|
||||
|
||||
public ImageProgressView(Context context) {
|
||||
super(context);
|
||||
// TODO Auto-generated constructor stub
|
||||
init();
|
||||
|
||||
}
|
||||
|
||||
public ImageProgressView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public ImageProgressView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init(){
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setColor(Color.GREEN);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStrokeWidth(20);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
|
||||
MeasureSpec.getSize(heightMeasureSpec));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
|
||||
super.onDraw(canvas);
|
||||
|
||||
if (circle == null)
|
||||
{
|
||||
circle = new RectF(getWidth()/2,getHeight()/2+getHeight()/8, getWidth()/3,getHeight()/3);
|
||||
}
|
||||
|
||||
float sweepAngle = 360f * progress;
|
||||
|
||||
canvas.drawArc(circle, 0, sweepAngle, true, paint);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,329 @@
|
|||
package org.torproject.android.ui;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.torproject.android.R;
|
||||
import org.torproject.android.service.TorResourceInstaller;
|
||||
import org.torproject.android.service.TorServiceConstants;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.StatFs;
|
||||
import android.text.format.Formatter;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.TextView;
|
||||
|
||||
|
||||
public class OrbotDiagnosticsActivity extends Activity {
|
||||
|
||||
private TextView mTextView = null;
|
||||
private final static String TAG = "OrbotDiag";
|
||||
private StringBuffer log = new StringBuffer();
|
||||
Process mProcess;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.layout_diag);
|
||||
|
||||
mTextView = (TextView)findViewById(R.id.diaglog);
|
||||
|
||||
}
|
||||
|
||||
private String getFreeStorage ()
|
||||
{
|
||||
File path = Environment.getDataDirectory();
|
||||
StatFs stat = new StatFs(path.getPath());
|
||||
long blockSize = stat.getBlockSize();
|
||||
long availableBlocks = stat.getAvailableBlocks();
|
||||
return Formatter.formatFileSize(this, availableBlocks * blockSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
stopTor();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
|
||||
super.onDestroy();
|
||||
|
||||
}
|
||||
|
||||
private void stopTor ()
|
||||
{
|
||||
File appBinHome = this.getDir("bin", Context.MODE_PRIVATE);
|
||||
|
||||
File fileTor= new File(appBinHome, TorServiceConstants.TOR_ASSET_KEY);
|
||||
|
||||
if (mProcess != null)
|
||||
mProcess.destroy();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
|
||||
log("Hello, Orbot!");
|
||||
|
||||
try
|
||||
{
|
||||
log(android.os.Build.DEVICE);
|
||||
log(android.os.Build.HARDWARE);
|
||||
log(android.os.Build.MANUFACTURER);
|
||||
log(android.os.Build.MODEL);
|
||||
log(android.os.Build.VERSION.CODENAME);
|
||||
log(android.os.Build.VERSION.RELEASE);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log("error getting device info");
|
||||
}
|
||||
|
||||
showFileTree ();
|
||||
|
||||
runTorTest();
|
||||
}
|
||||
|
||||
private void runTorTest ()
|
||||
{
|
||||
try
|
||||
{
|
||||
File appBinHome = this.getDir("bin", Context.MODE_PRIVATE);
|
||||
File appDataHome = this.getDir("data", Context.MODE_PRIVATE);
|
||||
|
||||
File fileTor= new File(appBinHome, TorServiceConstants.TOR_ASSET_KEY);
|
||||
enableBinExec (fileTor, appBinHome);
|
||||
|
||||
InputStream is = getResources().openRawResource(R.raw.torrc);
|
||||
File fileTorrc = new File(appBinHome, TorServiceConstants.TORRC_ASSET_KEY + "diag");
|
||||
TorResourceInstaller.streamToFile(is,fileTorrc, false, false);
|
||||
|
||||
/**
|
||||
ArrayList<String> alEnv = new ArrayList<String>();
|
||||
alEnv.add("HOME=" + appBinHome.getAbsolutePath());
|
||||
Shell shell = Shell.startShell(alEnv,appBinHome.getAbsolutePath());
|
||||
SimpleCommand cmdTor = new SimpleCommand(fileTor.getAbsolutePath() + " DataDirectory " + appDataHome.getAbsolutePath() + " -f " + fileTorrc.getAbsolutePath());
|
||||
shell.add(cmdTor);
|
||||
**/
|
||||
|
||||
String cmd = fileTor.getAbsolutePath() + " DataDirectory " + appDataHome.getAbsolutePath() + " -f " + fileTorrc.getAbsolutePath();
|
||||
|
||||
log ("Executing command> " + cmd);
|
||||
|
||||
mProcess = Runtime.getRuntime().exec(cmd);
|
||||
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(mProcess.getInputStream()));
|
||||
StreamGobbler sg = new StreamGobbler();
|
||||
sg.reader = bufferedReader;
|
||||
sg.process = mProcess;
|
||||
new Thread(sg).start();
|
||||
|
||||
if (mProcess.getErrorStream() != null)
|
||||
{
|
||||
bufferedReader = new BufferedReader(new InputStreamReader(mProcess.getErrorStream()));
|
||||
sg = new StreamGobbler();
|
||||
sg.reader = bufferedReader;
|
||||
sg.process = mProcess;
|
||||
new Thread(sg).start();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.d(TAG,"runTorTest exception",e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class StreamGobbler implements Runnable
|
||||
{
|
||||
BufferedReader reader;
|
||||
Process process;
|
||||
|
||||
public void run ()
|
||||
{
|
||||
String line = null;
|
||||
try {
|
||||
while ( (line = reader.readLine()) != null)
|
||||
{
|
||||
Message msg = mHandler.obtainMessage(0);
|
||||
msg.getData().putString("log", line);
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "error reading line",e);
|
||||
}
|
||||
|
||||
//log("Tor exit code=" + process.exitValue() + ";");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private boolean enableBinExec (File fileBin, File appBinHome) throws Exception
|
||||
{
|
||||
|
||||
log(fileBin.getName() + ": PRE: Is binary exec? " + fileBin.canExecute());
|
||||
|
||||
if (!fileBin.canExecute())
|
||||
{
|
||||
log("(re)Setting permission on binary: " + fileBin.getAbsolutePath());
|
||||
|
||||
Runtime.getRuntime().exec("chmod " + TorServiceConstants.CHMOD_EXE_VALUE + ' ' + fileBin.getAbsolutePath()).waitFor();
|
||||
|
||||
File fileTest = new File(fileBin.getAbsolutePath());
|
||||
log(fileTest.getName() + ": POST: Is binary exec? " + fileTest.canExecute());
|
||||
|
||||
}
|
||||
|
||||
return fileBin.canExecute();
|
||||
}
|
||||
|
||||
private void showFileTree ()
|
||||
{
|
||||
|
||||
File fileDir = this.getDir("bin", Context.MODE_PRIVATE);
|
||||
|
||||
if (fileDir.exists())
|
||||
{
|
||||
log("checking file tree: " + fileDir.getAbsolutePath());
|
||||
printDir (fileDir.getName(), fileDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
log("app_bin does not exist");
|
||||
}
|
||||
|
||||
fileDir = this.getDir("data", Context.MODE_PRIVATE);
|
||||
if (fileDir.exists())
|
||||
{
|
||||
log("checking file tree: " + fileDir.getAbsolutePath());
|
||||
printDir (fileDir.getName(), fileDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
log ("app_data does not exist");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void printDir (String path, File fileDir)
|
||||
{
|
||||
File[] files = fileDir.listFiles();
|
||||
|
||||
if (files != null && files.length > 0)
|
||||
{
|
||||
for (File file : files)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
if (file.isDirectory())
|
||||
{
|
||||
printDir(path + '/' + file.getName(), file);
|
||||
}
|
||||
else
|
||||
{
|
||||
log(path + '/' + file.getName() + " len:" + file.length() + " exec:" + file.canExecute());
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log("problem printing out file information");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Handler mHandler = new Handler ()
|
||||
{
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
|
||||
super.handleMessage(msg);
|
||||
|
||||
String logMsg = msg.getData().getString("log");
|
||||
log(logMsg);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private void log (String msg)
|
||||
{
|
||||
Log.d(TAG, msg);
|
||||
mTextView.append(msg + '\n');
|
||||
log.append(msg + '\n');
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Inflate menu resource file.
|
||||
getMenuInflater().inflate(R.menu.share_menu, menu);
|
||||
|
||||
// Locate MenuItem with ShareActionProvider
|
||||
MenuItem item = menu.findItem(R.id.menu_item_share);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case R.id.menu_item_share:
|
||||
sendLog();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendLog ()
|
||||
{
|
||||
int maxLength = 5000;
|
||||
|
||||
String logShare = null;
|
||||
|
||||
if (log.length() > maxLength)
|
||||
logShare = log.substring(0, maxLength);
|
||||
else
|
||||
logShare = log.toString();
|
||||
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, logShare);
|
||||
sendIntent.setType("text/plain");
|
||||
startActivity(sendIntent);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
package org.torproject.android.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.torproject.android.service.OrbotConstants;
|
||||
import org.torproject.android.R;
|
||||
import org.torproject.android.service.TorServiceConstants;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PromoAppsActivity extends Activity implements OrbotConstants {
|
||||
|
||||
final static String MARKET_URI = "market://details?id=";
|
||||
final static String FDROID_APP_URI = "https://f-droid.org/repository/browse/?fdid=";
|
||||
final static String PLAY_APP_URI = "https://play.google.com/store/apps/details?id=";
|
||||
final static String FDROID_URI = "https://f-droid.org/repository/browse/?fdfilter=info.guardianproject";
|
||||
final static String PLAY_URI = "https://play.google.com/store/apps/developer?id=The+Guardian+Project";
|
||||
|
||||
private final static String FDROID_PACKAGE_NAME = "org.fdroid.fdroid";
|
||||
private final static String PLAY_PACKAGE_NAME = "com.android.vending";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
|
||||
super.onStart();
|
||||
setContentView(R.layout.layout_promo_apps);
|
||||
|
||||
stepFive();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
|
||||
}
|
||||
|
||||
void stepFive(){
|
||||
|
||||
|
||||
String title = getString(R.string.wizard_tips_title);
|
||||
|
||||
setTitle(title);
|
||||
|
||||
Button btnLink = (Button)findViewById(R.id.WizardRootButtonInstallGibberbot);
|
||||
|
||||
btnLink.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
finish();
|
||||
startActivity(getInstallIntent("info.guardianproject.otr.app.im"));
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
btnLink = (Button)findViewById(R.id.WizardRootButtonInstallOrweb);
|
||||
|
||||
btnLink.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
finish();
|
||||
startActivity(getInstallIntent(TorServiceConstants.BROWSER_APP_USERNAME));
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
btnLink = (Button)findViewById(R.id.WizardRootButtonInstallDuckgo);
|
||||
|
||||
btnLink.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
|
||||
finish();
|
||||
startActivity(getInstallIntent("com.duckduckgo.mobile.android"));
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
btnLink = (Button)findViewById(R.id.WizardRootButtonInstallTwitter);
|
||||
|
||||
btnLink.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
|
||||
String url = getString(R.string.twitter_setup_url);
|
||||
finish();
|
||||
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
btnLink = (Button)findViewById(R.id.WizardRootButtonInstallStoryMaker);
|
||||
|
||||
btnLink.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
finish();
|
||||
startActivity(getInstallIntent("info.guardianproject.mrapp"));
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
btnLink = (Button)findViewById(R.id.WizardRootButtonInstallMartus);
|
||||
|
||||
btnLink.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
finish();
|
||||
startActivity(getInstallIntent("org.martus.android"));
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
btnLink = (Button)findViewById(R.id.WizardRootButtonGooglePlay);
|
||||
PackageManager pm = getPackageManager();
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
// change text and icon based on which app store is installed (or not)
|
||||
try {
|
||||
if (isAppInstalled(pm, FDROID_PACKAGE_NAME)) {
|
||||
Drawable icon = pm.getApplicationIcon(FDROID_PACKAGE_NAME);
|
||||
btnLink.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
|
||||
btnLink.setText(R.string.wizard_tips_fdroid);
|
||||
intent.setPackage(FDROID_PACKAGE_NAME);
|
||||
intent.setData(Uri.parse(FDROID_URI));
|
||||
} else if (isAppInstalled(pm, PLAY_PACKAGE_NAME)) {
|
||||
Drawable icon = pm.getApplicationIcon(PLAY_PACKAGE_NAME);
|
||||
btnLink.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
|
||||
btnLink.setText(R.string.wizard_tips_play);
|
||||
intent.setPackage(PLAY_PACKAGE_NAME);
|
||||
intent.setData(Uri.parse(PLAY_URI));
|
||||
}
|
||||
} catch (NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
btnLink.setText(R.string.wizard_tips_fdroid_org);
|
||||
intent.setData(Uri.parse(FDROID_URI));
|
||||
}
|
||||
|
||||
btnLink.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
Button next = ((Button)findViewById(R.id.btnWizard2));
|
||||
next.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
boolean isAppInstalled(PackageManager pm, String packageName) {
|
||||
try {
|
||||
pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
|
||||
return true;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Intent getInstallIntent(String packageName) {
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(MARKET_URI + packageName));
|
||||
|
||||
PackageManager pm = getPackageManager();
|
||||
List<ResolveInfo> resInfos = pm.queryIntentActivities(intent, 0);
|
||||
|
||||
String foundPackageName = null;
|
||||
for (ResolveInfo r : resInfos) {
|
||||
Log.i(TAG, "market: " + r.activityInfo.packageName);
|
||||
if (TextUtils.equals(r.activityInfo.packageName, FDROID_PACKAGE_NAME)
|
||||
|| TextUtils.equals(r.activityInfo.packageName, PLAY_PACKAGE_NAME)) {
|
||||
foundPackageName = r.activityInfo.packageName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundPackageName == null) {
|
||||
intent.setData(Uri.parse(FDROID_APP_URI + packageName));
|
||||
} else {
|
||||
intent.setPackage(foundPackageName);
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package org.torproject.android.ui;
|
||||
|
||||
import android.graphics.Camera;
|
||||
import android.graphics.Matrix;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.Transformation;
|
||||
|
||||
/**
|
||||
* An animation that rotates the view on the Y axis between two specified angles.
|
||||
* This animation also adds a translation on the Z axis (depth) to improve the effect.
|
||||
*/
|
||||
public class Rotate3dAnimation extends Animation {
|
||||
private final float mFromDegrees;
|
||||
private final float mToDegrees;
|
||||
private final float mCenterX;
|
||||
private final float mCenterY;
|
||||
private final float mDepthZ;
|
||||
private final boolean mReverse;
|
||||
private Camera mCamera;
|
||||
|
||||
/**
|
||||
* Creates a new 3D rotation on the Y axis. The rotation is defined by its
|
||||
* start angle and its end angle. Both angles are in degrees. The rotation
|
||||
* is performed around a center point on the 2D space, definied by a pair
|
||||
* of X and Y coordinates, called centerX and centerY. When the animation
|
||||
* starts, a translation on the Z axis (depth) is performed. The length
|
||||
* of the translation can be specified, as well as whether the translation
|
||||
* should be reversed in time.
|
||||
*
|
||||
* @param fromDegrees the start angle of the 3D rotation
|
||||
* @param toDegrees the end angle of the 3D rotation
|
||||
* @param centerX the X center of the 3D rotation
|
||||
* @param centerY the Y center of the 3D rotation
|
||||
* @param reverse true if the translation should be reversed, false otherwise
|
||||
*/
|
||||
public Rotate3dAnimation(float fromDegrees, float toDegrees,
|
||||
float centerX, float centerY, float depthZ, boolean reverse) {
|
||||
mFromDegrees = fromDegrees;
|
||||
mToDegrees = toDegrees;
|
||||
mCenterX = centerX;
|
||||
mCenterY = centerY;
|
||||
mDepthZ = depthZ;
|
||||
mReverse = reverse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(int width, int height, int parentWidth, int parentHeight) {
|
||||
super.initialize(width, height, parentWidth, parentHeight);
|
||||
mCamera = new Camera();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyTransformation(float interpolatedTime, Transformation t) {
|
||||
final float fromDegrees = mFromDegrees;
|
||||
float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
|
||||
|
||||
final float centerX = mCenterX;
|
||||
final float centerY = mCenterY;
|
||||
final Camera camera = mCamera;
|
||||
|
||||
final Matrix matrix = t.getMatrix();
|
||||
|
||||
camera.save();
|
||||
if (mReverse) {
|
||||
camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
|
||||
} else {
|
||||
camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
|
||||
}
|
||||
camera.rotateY(degrees);
|
||||
camera.getMatrix(matrix);
|
||||
camera.restore();
|
||||
|
||||
matrix.preTranslate(-centerX, -centerY);
|
||||
matrix.postTranslate(centerX, centerY);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.torproject.android.ui;
|
||||
|
||||
public class VPNSetupActivity {
|
||||
|
||||
}
|
|
@ -0,0 +1,452 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.torproject.android.vpn;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.torproject.android.OrbotApp;
|
||||
import org.torproject.android.R;
|
||||
import org.torproject.android.service.TorService;
|
||||
import org.torproject.android.service.TorServiceConstants;
|
||||
import org.torproject.android.service.TorServiceUtils;
|
||||
import org.torproject.android.service.TorifiedApp;
|
||||
import org.torproject.android.ui.AppManager;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Application;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.net.VpnService;
|
||||
import android.net.VpnService.Builder;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.runjva.sourceforge.jsocks.protocol.ProxyServer;
|
||||
import com.runjva.sourceforge.jsocks.server.ServerAuthenticatorNone;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||
public class OrbotVpnManager implements Handler.Callback {
|
||||
private static final String TAG = "OrbotVpnService";
|
||||
|
||||
private PendingIntent mConfigureIntent;
|
||||
|
||||
private Thread mThreadVPN;
|
||||
|
||||
private String mSessionName = "OrbotVPN";
|
||||
private ParcelFileDescriptor mInterface;
|
||||
|
||||
private int mTorSocks = TorServiceConstants.SOCKS_PROXY_PORT_DEFAULT;
|
||||
|
||||
public static int sSocksProxyServerPort = -1;
|
||||
public static String sSocksProxyLocalhost = null;
|
||||
private ProxyServer mSocksProxyServer;
|
||||
|
||||
|
||||
private final static int VPN_MTU = 1500;
|
||||
|
||||
private final static boolean mIsLollipop = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
|
||||
|
||||
//this is the actual DNS server we talk to over UDP or TCP (now using Tor's DNS port)
|
||||
private final static String DEFAULT_ACTUAL_DNS_HOST = "127.0.0.1";
|
||||
private final static int DEFAULT_ACTUAL_DNS_PORT = TorServiceConstants.TOR_DNS_PORT_DEFAULT;
|
||||
|
||||
private boolean isRestart = false;
|
||||
|
||||
private VpnService mService;
|
||||
|
||||
|
||||
static{
|
||||
System.loadLibrary("tun2socks");
|
||||
}
|
||||
|
||||
public OrbotVpnManager (VpnService service)
|
||||
{
|
||||
mService = service;
|
||||
}
|
||||
|
||||
//public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
public int handleIntent(Builder builder, Intent intent) {
|
||||
|
||||
if (intent != null)
|
||||
{
|
||||
String action = intent.getAction();
|
||||
|
||||
if (action.equals("start"))
|
||||
{
|
||||
|
||||
// Stop the previous session by interrupting the thread.
|
||||
if (mThreadVPN == null || (!mThreadVPN.isAlive()))
|
||||
{
|
||||
Log.d(TAG,"starting OrbotVPNService service!");
|
||||
|
||||
mTorSocks = intent.getIntExtra("torSocks", TorServiceConstants.SOCKS_PROXY_PORT_DEFAULT);
|
||||
|
||||
if (!mIsLollipop)
|
||||
{
|
||||
|
||||
startSocksBypass();
|
||||
}
|
||||
|
||||
setupTun2Socks(builder);
|
||||
}
|
||||
}
|
||||
else if (action.equals("stop"))
|
||||
{
|
||||
Log.d(TAG,"stop OrbotVPNService service!");
|
||||
|
||||
stopVPN();
|
||||
//if (mHandler != null)
|
||||
//mHandler.postDelayed(new Runnable () { public void run () { stopSelf(); }}, 1000);
|
||||
}
|
||||
else if (action.equals("refresh"))
|
||||
{
|
||||
Log.d(TAG,"refresh OrbotVPNService service!");
|
||||
|
||||
if (!mIsLollipop)
|
||||
startSocksBypass();
|
||||
|
||||
if (!isRestart)
|
||||
setupTun2Socks(builder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return Service.START_STICKY;
|
||||
}
|
||||
|
||||
private void startSocksBypass()
|
||||
{
|
||||
|
||||
new Thread ()
|
||||
{
|
||||
|
||||
public void run ()
|
||||
{
|
||||
|
||||
//generate the proxy port that the
|
||||
if (sSocksProxyServerPort == -1)
|
||||
{
|
||||
try {
|
||||
|
||||
sSocksProxyLocalhost = "127.0.0.1";// InetAddress.getLocalHost().getHostAddress();
|
||||
sSocksProxyServerPort = (int)((Math.random()*1000)+10000);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG,"Unable to access localhost",e);
|
||||
throw new RuntimeException("Unable to access localhost: " + e);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (mSocksProxyServer != null)
|
||||
{
|
||||
stopSocksBypass ();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
mSocksProxyServer = new ProxyServer(new ServerAuthenticatorNone(null, null));
|
||||
ProxyServer.setVpnService(mService);
|
||||
mSocksProxyServer.start(sSocksProxyServerPort, 5, InetAddress.getLocalHost());
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(TAG,"error getting host",e);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
|
||||
}
|
||||
|
||||
private synchronized void stopSocksBypass ()
|
||||
{
|
||||
|
||||
if (mSocksProxyServer != null){
|
||||
mSocksProxyServer.stop();
|
||||
mSocksProxyServer = null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// Set the locale to English (or probably any other language that^M
|
||||
// uses Hindu-Arabic (aka Latin) numerals).^M
|
||||
// We have found that VpnService.Builder does something locale-dependent^M
|
||||
// internally that causes errors when the locale uses its own numerals^M
|
||||
// (i.e., Farsi and Arabic).^M
|
||||
Locale.setDefault(new Locale("en"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
stopVPN();
|
||||
}*/
|
||||
|
||||
private void stopVPN ()
|
||||
{
|
||||
if (mIsLollipop)
|
||||
stopSocksBypass ();
|
||||
|
||||
if (mInterface != null){
|
||||
try
|
||||
{
|
||||
Log.d(TAG,"closing interface, destroying VPN interface");
|
||||
|
||||
mInterface.close();
|
||||
mInterface = null;
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.d(TAG,"error stopping tun2socks",e);
|
||||
}
|
||||
catch (Error e)
|
||||
{
|
||||
Log.d(TAG,"error stopping tun2socks",e);
|
||||
}
|
||||
}
|
||||
|
||||
Tun2Socks.Stop();
|
||||
|
||||
try {
|
||||
TorServiceUtils.killProcess(filePdnsd);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
mThreadVPN = null;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message message) {
|
||||
if (message != null) {
|
||||
Toast.makeText(mService, message.what, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private synchronized void setupTun2Socks(final Builder builder) {
|
||||
|
||||
|
||||
if (mInterface != null) //stop tun2socks now to give it time to clean up
|
||||
{
|
||||
isRestart = true;
|
||||
Tun2Socks.Stop();
|
||||
}
|
||||
|
||||
mThreadVPN = new Thread ()
|
||||
{
|
||||
|
||||
public void run ()
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
if (isRestart)
|
||||
{
|
||||
Log.d(TAG,"is a restart... let's wait for a few seconds");
|
||||
Thread.sleep(3000);
|
||||
}
|
||||
|
||||
//start PDNSD daemon pointing to actual DNS
|
||||
startDNS(DEFAULT_ACTUAL_DNS_HOST,DEFAULT_ACTUAL_DNS_PORT);
|
||||
|
||||
final String vpnName = "OrbotVPN";
|
||||
final String localhost = "127.0.0.1";
|
||||
|
||||
final String virtualGateway = "10.10.10.1";
|
||||
final String virtualIP = "10.10.10.2";
|
||||
final String virtualNetMask = "255.255.255.0";
|
||||
final String dummyDNS = "8.8.8.8"; //this is intercepted by the tun2socks library, but we must put in a valid DNS to start
|
||||
final String defaultRoute = "0.0.0.0";
|
||||
|
||||
final String localSocks = localhost + ':'
|
||||
+ String.valueOf(mTorSocks);
|
||||
|
||||
final String localDNS = virtualGateway + ':' + "8091";//String.valueOf(TorServiceConstants.TOR_DNS_PORT_DEFAULT);
|
||||
final boolean localDnsTransparentProxy = true;
|
||||
|
||||
builder.setMtu(VPN_MTU);
|
||||
builder.addAddress(virtualGateway,32);
|
||||
|
||||
builder.setSession(vpnName);
|
||||
|
||||
builder.addDnsServer(dummyDNS);
|
||||
builder.addRoute(dummyDNS,32);
|
||||
|
||||
//route all traffic through VPN (we might offer country specific exclude lists in the future)
|
||||
builder.addRoute(defaultRoute,0);
|
||||
|
||||
//handle ipv6
|
||||
//builder.addAddress("fdfe:dcba:9876::1", 126);
|
||||
//builder.addRoute("::", 0);
|
||||
|
||||
if (mIsLollipop)
|
||||
doLollipopAppRouting(builder);
|
||||
|
||||
// Create a new interface using the builder and save the parameters.
|
||||
ParcelFileDescriptor newInterface = builder.setSession(mSessionName)
|
||||
.setConfigureIntent(mConfigureIntent)
|
||||
.establish();
|
||||
|
||||
if (mInterface != null)
|
||||
{
|
||||
Log.d(TAG,"Stopping existing VPN interface");
|
||||
mInterface.close();
|
||||
mInterface = null;
|
||||
}
|
||||
|
||||
mInterface = newInterface;
|
||||
|
||||
Tun2Socks.Start(mInterface, VPN_MTU, virtualIP, virtualNetMask, localSocks , localDNS , localDnsTransparentProxy);
|
||||
|
||||
isRestart = false;
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.d(TAG,"tun2Socks has stopped",e);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
mThreadVPN.start();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private void doLollipopAppRouting (Builder builder) throws NameNotFoundException
|
||||
{
|
||||
|
||||
ArrayList<TorifiedApp> apps = AppManager.getApps(mService, TorServiceUtils.getSharedPrefs(mService.getApplicationContext()));
|
||||
|
||||
boolean perAppEnabled = false;
|
||||
|
||||
for (TorifiedApp app : apps)
|
||||
{
|
||||
if (app.isTorified() && (!app.getPackageName().equals(mService.getPackageName())))
|
||||
{
|
||||
builder.addAllowedApplication(app.getPackageName());
|
||||
perAppEnabled = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!perAppEnabled)
|
||||
builder.addDisallowedApplication(mService.getPackageName());
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void onRevoke() {
|
||||
|
||||
Log.w(TAG,"VPNService REVOKED!");
|
||||
|
||||
if (!isRestart)
|
||||
{
|
||||
SharedPreferences prefs = TorServiceUtils.getSharedPrefs(mService.getApplicationContext());
|
||||
prefs.edit().putBoolean("pref_vpn", false).commit();
|
||||
stopVPN();
|
||||
}
|
||||
|
||||
isRestart = false;
|
||||
|
||||
//super.onRevoke();
|
||||
|
||||
}
|
||||
|
||||
File filePdnsd = null;
|
||||
|
||||
private void startDNS (String dns, int port) throws IOException, TimeoutException
|
||||
{
|
||||
File filePdnsd = null;//getDir(TorServiceConstants.DIRECTORY_TOR_BINARY, Application.MODE_PRIVATE);
|
||||
|
||||
makePdnsdConf(mService, dns, port,filePdnsd.getParentFile() );
|
||||
|
||||
ArrayList<String> customEnv = new ArrayList<String>();
|
||||
String baseDirectory = filePdnsd.getParent();
|
||||
|
||||
String cmdString = "sh " + filePdnsd.getCanonicalPath() +
|
||||
" -c " + baseDirectory + "/pdnsd.conf";
|
||||
|
||||
Process proc = Runtime.getRuntime().exec(cmdString);
|
||||
try { proc.waitFor();} catch (Exception e){}
|
||||
|
||||
Log.i(TAG,"PDNSD: " + proc.exitValue());
|
||||
|
||||
}
|
||||
|
||||
public static void makePdnsdConf(Context context, String dns, int port, File fileDir) throws FileNotFoundException {
|
||||
String conf = String.format(context.getString(R.string.pdnsd_conf), dns, port);
|
||||
|
||||
File f = new File(fileDir,"pdnsd.conf");
|
||||
|
||||
if (f.exists()) {
|
||||
f.delete();
|
||||
}
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(f, false);
|
||||
PrintStream ps = new PrintStream(fos);
|
||||
ps.print(conf);
|
||||
ps.close();
|
||||
|
||||
//f.withWriter { out -> out.print conf };
|
||||
|
||||
|
||||
File cache = new File(fileDir,"pdnsd.cache");
|
||||
|
||||
if (!cache.exists()) {
|
||||
try {
|
||||
cache.createNewFile();
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package org.torproject.android.vpn;
|
||||
|
||||
/*
|
||||
* Copyright (c) 2013, 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
|
||||
public class Tun2Socks
|
||||
{
|
||||
public static interface IProtectSocket
|
||||
{
|
||||
boolean doVpnProtect(Socket socket);
|
||||
boolean doVpnProtect(DatagramSocket socket);
|
||||
};
|
||||
|
||||
private static final String TAG = Tun2Socks.class.getSimpleName();
|
||||
private static final boolean LOGD = true;
|
||||
|
||||
private static Thread mThread;
|
||||
private static ParcelFileDescriptor mVpnInterfaceFileDescriptor;
|
||||
private static int mVpnInterfaceMTU;
|
||||
private static String mVpnIpAddress;
|
||||
private static String mVpnNetMask;
|
||||
private static String mSocksServerAddress;
|
||||
private static String mUdpgwServerAddress;
|
||||
private static boolean mUdpgwTransparentDNS;
|
||||
|
||||
// Note: this class isn't a singleton, but you can't run more
|
||||
// than one instance due to the use of global state (the lwip
|
||||
// module, etc.) in the native code.
|
||||
|
||||
private static boolean mLibLoaded = false;
|
||||
|
||||
public static void Start(
|
||||
ParcelFileDescriptor vpnInterfaceFileDescriptor,
|
||||
int vpnInterfaceMTU,
|
||||
String vpnIpAddress,
|
||||
String vpnNetMask,
|
||||
String socksServerAddress,
|
||||
String udpgwServerAddress,
|
||||
boolean udpgwTransparentDNS)
|
||||
{
|
||||
|
||||
if (!mLibLoaded)
|
||||
{
|
||||
System.loadLibrary("tun2socks");
|
||||
mLibLoaded = true;
|
||||
}
|
||||
|
||||
mVpnInterfaceFileDescriptor = vpnInterfaceFileDescriptor;
|
||||
mVpnInterfaceMTU = vpnInterfaceMTU;
|
||||
mVpnIpAddress = vpnIpAddress;
|
||||
mVpnNetMask = vpnNetMask;
|
||||
mSocksServerAddress = socksServerAddress;
|
||||
mUdpgwServerAddress = udpgwServerAddress;
|
||||
mUdpgwTransparentDNS = udpgwTransparentDNS;
|
||||
|
||||
if (mVpnInterfaceFileDescriptor != null)
|
||||
runTun2Socks(
|
||||
mVpnInterfaceFileDescriptor.detachFd(),
|
||||
mVpnInterfaceMTU,
|
||||
mVpnIpAddress,
|
||||
mVpnNetMask,
|
||||
mSocksServerAddress,
|
||||
mUdpgwServerAddress,
|
||||
mUdpgwTransparentDNS ? 1 : 0);
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
|
||||
terminateTun2Socks();
|
||||
|
||||
}
|
||||
|
||||
public static void logTun2Socks(
|
||||
String level,
|
||||
String channel,
|
||||
String msg)
|
||||
{
|
||||
String logMsg = level + "(" + channel + "): " + msg;
|
||||
if (0 == level.compareTo("ERROR"))
|
||||
{
|
||||
Log.e(TAG, logMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOGD) Log.d(TAG, logMsg);
|
||||
}
|
||||
}
|
||||
|
||||
private native static int runTun2Socks(
|
||||
int vpnInterfaceFileDescriptor,
|
||||
int vpnInterfaceMTU,
|
||||
String vpnIpAddress,
|
||||
String vpnNetMask,
|
||||
String socksServerAddress,
|
||||
String udpgwServerAddress,
|
||||
int udpgwTransparentDNS);
|
||||
|
||||
private native static void terminateTun2Socks();
|
||||
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
package org.torproject.android.vpn;
|
||||
|
||||
import org.torproject.android.R;
|
||||
import org.torproject.android.service.Prefs;
|
||||
import org.torproject.android.service.TorService;
|
||||
import org.torproject.android.service.TorServiceConstants;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.VpnService;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
|
||||
/*
|
||||
* To combat background service being stopped/swiped
|
||||
*/
|
||||
public class VPNEnableActivity extends Activity {
|
||||
|
||||
private final static int REQUEST_VPN = 7777;
|
||||
private Intent intent = null;
|
||||
private boolean checkVpn = true;
|
||||
private Handler h = new Handler();
|
||||
|
||||
@Override
|
||||
public void onCreate( Bundle icicle ) {
|
||||
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
//getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
|
||||
super.onCreate( icicle );
|
||||
|
||||
Log.d("VPNEnableActivity","prompting user to start Orbot VPN");
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void onResume ()
|
||||
{
|
||||
super.onResume();
|
||||
|
||||
if (checkVpn)
|
||||
{
|
||||
intent = VpnService.prepare(this);
|
||||
|
||||
if (intent != null)
|
||||
promptStartVpnService();
|
||||
else
|
||||
startVpnService ();
|
||||
|
||||
checkVpn = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void promptStartVpnService ()
|
||||
{
|
||||
|
||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||
.setTitle(getString(R.string.app_name) + ' ' + getString(R.string.apps_mode))
|
||||
.setMessage(getString(R.string.you_can_enable_all_apps_on_your_device_to_run_through_the_tor_network_using_the_vpn_feature_of_android_))
|
||||
.setPositiveButton(R.string.activate, new Dialog.OnClickListener ()
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Prefs.putUseVpn(true);
|
||||
|
||||
startVpnService();
|
||||
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
.setNegativeButton(R.string.btn_cancel, new Dialog.OnClickListener ()
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
h.postDelayed(new Runnable () {
|
||||
|
||||
public void run ()
|
||||
{
|
||||
VPNEnableActivity.this.finish();
|
||||
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
}).create();
|
||||
|
||||
dialog.show();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void startVpnService ()
|
||||
{
|
||||
if (intent == null)
|
||||
{
|
||||
Log.d("VPNEnableActivity","VPN enabled, starting Tor...");
|
||||
sendIntentToService(TorServiceConstants.CMD_VPN);
|
||||
|
||||
Handler h = new Handler();
|
||||
h.postDelayed(new Runnable () {
|
||||
|
||||
public void run ()
|
||||
{
|
||||
sendIntentToService(TorServiceConstants.ACTION_START);
|
||||
finish();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.w("VPNEnableActivity","prompt for VPN");
|
||||
startActivityForResult(intent,REQUEST_VPN);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int response, Intent data) {
|
||||
super.onActivityResult(request, response, data);
|
||||
|
||||
if (request == REQUEST_VPN && response == RESULT_OK)
|
||||
{
|
||||
sendIntentToService(TorServiceConstants.CMD_VPN);
|
||||
|
||||
h.postDelayed(new Runnable () {
|
||||
|
||||
public void run ()
|
||||
{
|
||||
sendIntentToService(TorServiceConstants.ACTION_START);
|
||||
finish();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void sendIntentToService(String action) {
|
||||
Intent torService = new Intent(this, TorService.class);
|
||||
torService.setAction(action);
|
||||
startService(torService);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
# Copyright (C) 2009 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
#
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
ROOT_PATH := $(LOCAL_PATH)
|
||||
EXTERN_PATH := $(LOCAL_PATH)/../external
|
||||
|
||||
########################################################
|
||||
## pdnsd library
|
||||
########################################################
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
PDNSD_SOURCES := $(wildcard $(LOCAL_PATH)/pdnsd/src/*.c)
|
||||
|
||||
LOCAL_MODULE := pdnsd
|
||||
LOCAL_SRC_FILES := $(PDNSD_SOURCES:$(LOCAL_PATH)/%=%)
|
||||
LOCAL_CFLAGS := -Wall -O2 -I$(LOCAL_PATH)/pdnsd -DHAVE_STPCPY
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
########################################################
|
||||
## libancillary
|
||||
########################################################
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
ANCILLARY_SOURCE := fd_recv.c fd_send.c
|
||||
|
||||
LOCAL_MODULE := libancillary
|
||||
LOCAL_CFLAGS := -O2 -I$(LOCAL_PATH)/libancillary
|
||||
|
||||
LOCAL_SRC_FILES := $(addprefix libancillary/, $(ANCILLARY_SOURCE))
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
|
||||
########################################################
|
||||
## tun2socks
|
||||
########################################################
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_CFLAGS := -std=gnu99
|
||||
LOCAL_CFLAGS += -DBADVPN_THREADWORK_USE_PTHREAD -DBADVPN_LINUX -DBADVPN_BREACTOR_BADVPN -D_GNU_SOURCE
|
||||
LOCAL_CFLAGS += -DBADVPN_USE_SELFPIPE -DBADVPN_USE_EPOLL
|
||||
LOCAL_CFLAGS += -DBADVPN_LITTLE_ENDIAN -DBADVPN_THREAD_SAFE
|
||||
LOCAL_CFLAGS += -DNDEBUG -DANDROID
|
||||
LOCAL_CFLAGS += -DTUN2SOCKS_JNI
|
||||
LOCAL_CFLAGS += -DPSIPHON
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := libancillary
|
||||
|
||||
LOCAL_C_INCLUDES:= \
|
||||
$(LOCAL_PATH)/libancillary \
|
||||
$(EXTERN_PATH)/badvpn/ \
|
||||
$(EXTERN_PATH)/badvpn/lwip/src/include/ipv4 \
|
||||
$(EXTERN_PATH)/badvpn/lwip/src/include/ipv6 \
|
||||
$(EXTERN_PATH)/badvpn/lwip/src/include \
|
||||
$(EXTERN_PATH)/badvpn/lwip/custom \
|
||||
|
||||
TUN2SOCKS_SOURCES := \
|
||||
base/BLog_syslog.c \
|
||||
system/BReactor_badvpn.c \
|
||||
system/BSignal.c \
|
||||
system/BConnection_unix.c \
|
||||
system/BTime.c \
|
||||
system/BUnixSignal.c \
|
||||
system/BNetwork.c \
|
||||
flow/StreamRecvInterface.c \
|
||||
flow/PacketRecvInterface.c \
|
||||
flow/PacketPassInterface.c \
|
||||
flow/StreamPassInterface.c \
|
||||
flow/SinglePacketBuffer.c \
|
||||
flow/BufferWriter.c \
|
||||
flow/PacketBuffer.c \
|
||||
flow/PacketStreamSender.c \
|
||||
flow/PacketPassConnector.c \
|
||||
flow/PacketProtoFlow.c \
|
||||
flow/PacketPassFairQueue.c \
|
||||
flow/PacketProtoEncoder.c \
|
||||
flow/PacketProtoDecoder.c \
|
||||
socksclient/BSocksClient.c \
|
||||
tuntap/BTap.c \
|
||||
lwip/src/core/timers.c \
|
||||
lwip/src/core/udp.c \
|
||||
lwip/src/core/memp.c \
|
||||
lwip/src/core/init.c \
|
||||
lwip/src/core/pbuf.c \
|
||||
lwip/src/core/tcp.c \
|
||||
lwip/src/core/tcp_out.c \
|
||||
lwip/src/core/netif.c \
|
||||
lwip/src/core/def.c \
|
||||
lwip/src/core/mem.c \
|
||||
lwip/src/core/tcp_in.c \
|
||||
lwip/src/core/stats.c \
|
||||
lwip/src/core/inet_chksum.c \
|
||||
lwip/src/core/ipv4/icmp.c \
|
||||
lwip/src/core/ipv4/igmp.c \
|
||||
lwip/src/core/ipv4/ip4_addr.c \
|
||||
lwip/src/core/ipv4/ip_frag.c \
|
||||
lwip/src/core/ipv4/ip4.c \
|
||||
lwip/src/core/ipv4/autoip.c \
|
||||
lwip/src/core/ipv6/ethip6.c \
|
||||
lwip/src/core/ipv6/inet6.c \
|
||||
lwip/src/core/ipv6/ip6_addr.c \
|
||||
lwip/src/core/ipv6/mld6.c \
|
||||
lwip/src/core/ipv6/dhcp6.c \
|
||||
lwip/src/core/ipv6/icmp6.c \
|
||||
lwip/src/core/ipv6/ip6.c \
|
||||
lwip/src/core/ipv6/ip6_frag.c \
|
||||
lwip/src/core/ipv6/nd6.c \
|
||||
lwip/custom/sys.c \
|
||||
tun2socks/tun2socks.c \
|
||||
base/DebugObject.c \
|
||||
base/BLog.c \
|
||||
base/BPending.c \
|
||||
system/BDatagram_unix.c \
|
||||
flowextra/PacketPassInactivityMonitor.c \
|
||||
tun2socks/SocksUdpGwClient.c \
|
||||
udpgw_client/UdpGwClient.c
|
||||
|
||||
LOCAL_MODULE := tun2socks
|
||||
|
||||
LOCAL_LDLIBS := -ldl -llog
|
||||
|
||||
LOCAL_SRC_FILES := $(addprefix ../external/badvpn/, $(TUN2SOCKS_SOURCES))
|
||||
|
||||
##include $(BUILD_EXECUTABLE)
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
# Import cpufeatures
|
||||
$(call import-module,android/cpufeatures)
|
|
@ -0,0 +1,4 @@
|
|||
APP_ABI := armeabi x86
|
||||
APP_PLATFORM := android-9
|
||||
APP_STL := stlport_static
|
||||
NDK_TOOLCHAIN_VERSION := 4.8
|
|
@ -0,0 +1,139 @@
|
|||
This library provide an easy interface to the black magic that can be done
|
||||
on Unix domain sockets, like passing file descriptors from one process to
|
||||
another.
|
||||
|
||||
Programs that uses this library should include the ancillary.h header file.
|
||||
Nothing else is required.
|
||||
|
||||
All functions of this library require the following header:
|
||||
|
||||
#include <ancillary.h>
|
||||
|
||||
At this time, the only ancillary data defined by the Single Unix
|
||||
Specification (v3) is file descriptors.
|
||||
|
||||
Passing file descriptors
|
||||
|
||||
int ancil_send_fd(socket, file_descriptor)
|
||||
int socket: the Unix socket
|
||||
int file_descriptor: the file descriptor
|
||||
Return value: 0 for success, -1 for failure.
|
||||
|
||||
Sends one file descriptor on a socket.
|
||||
In case of failure, errno is set; the possible values are the ones of the
|
||||
sendmsg(2) system call.
|
||||
|
||||
|
||||
int ancil_recv_fd(socket, file_descriptor)
|
||||
int socket: the Unix socket
|
||||
int *file_descriptor: pointer to the returned file descriptor
|
||||
Return value: 0 for success, -1 for failure
|
||||
|
||||
Receives one file descriptor from a socket.
|
||||
In case of success, the file descriptor is stored in the integer pointed
|
||||
to by file_descriptor.
|
||||
In case of failure, errno is set; the possible values are the ones of the
|
||||
recvmsg(2) system call.
|
||||
The behavior is undefined if the recv_fd does not match a send_fd* on the
|
||||
other side.
|
||||
|
||||
|
||||
int ancil_send_fds(socket, file_descriptors, num_file_descriptors)
|
||||
int socket: the Unix socket
|
||||
const int *file_descriptors: array of file descriptors
|
||||
unsigned num_file_descriptors: number of file descriptors
|
||||
Return value: 0 for success, -1 for failure
|
||||
|
||||
Sends several file descriptors on a socket.
|
||||
In case of failure, errno is set; the possible values are the ones of the
|
||||
sendmsg(2) system call.
|
||||
The maximum number of file descriptors that can be sent using this
|
||||
function is ANCIL_MAX_N_FDS; the behavior is undefined in case of
|
||||
overflow, probably a stack corruption.
|
||||
|
||||
|
||||
int ancil_recv_fds(socket, file_descriptors, num_file_descriptors)
|
||||
int socket: the Unix socket
|
||||
int *file_descriptors: return array of file descriptors
|
||||
unsigned num_file_descriptors: number of file descriptors
|
||||
Return value: number of received fd for success, -1 for failure
|
||||
|
||||
Receives several file descriptors from a socket, no more than
|
||||
num_file_descriptors.
|
||||
In case of success, the received file descriptors are stored in the array
|
||||
pointed to by file_descriptors.
|
||||
In case of failure, errno is set; the possible values are the ones of the
|
||||
recvmsg(2) system call.
|
||||
The maximum number of file descriptors that can be received using this
|
||||
function is ANCIL_MAX_N_FDS; the behavior is undefined in case of
|
||||
overflow, probably a stack corruption.
|
||||
The behavior is undefined if the recv_fds does not match a send_fd* on
|
||||
the other side, or if the number of received file descriptors is more than
|
||||
num_file_descriptors.
|
||||
|
||||
|
||||
int ancil_send_fds_with_buffer(socket, fds, num, buffer)
|
||||
int socket: the Unix socket
|
||||
const int *fds: array of file descriptors
|
||||
unsigned num: number of file descriptors
|
||||
void *buffer: buffer to hold the system data structures
|
||||
Return value: 0 for success, -1 for failure
|
||||
|
||||
Sends several file descriptors on a socket.
|
||||
In case of failure, errno is set; the possible values are the ones of the
|
||||
sendmsg(2) system call.
|
||||
The buffer argument must point to a memory area large enough to hold the
|
||||
system data structures, see ANCIL_FD_BUFFER.
|
||||
|
||||
|
||||
int ancil_send_fds_with_buffer(socket, fds, num, buffer)
|
||||
int socket: the Unix socket
|
||||
int *fds: return array of file descriptors
|
||||
unsigned num: number of file descriptors
|
||||
void *buffer: buffer to hold the system data structures
|
||||
Return value: number of received fd for success, -1 for failure
|
||||
|
||||
Receives several file descriptors from a socket, no more than
|
||||
num_file_descriptors.
|
||||
In case of success, the received file descriptors are stored in the array
|
||||
pointed to by file_descriptors.
|
||||
In case of failure, errno is set; the possible values are the ones of the
|
||||
recvmsg(2) system call.
|
||||
The behavior is undefined if the recv_fds does not match a send_fd* on
|
||||
the other side, or if the number of received file descriptors is more than
|
||||
num_file_descriptors.
|
||||
The buffer argument must point to a memory area large enough to hold the
|
||||
system data structures, see ANCIL_FD_BUFFER.
|
||||
|
||||
|
||||
ANCIL_MAX_N_FDS
|
||||
|
||||
Maximum number of file descriptors that can be sent with the sent_fds and
|
||||
recv_fds functions. If you have to send more at once, use the
|
||||
*_with_buffer versions. The value is enough to send "quite a few" file
|
||||
descriptors.
|
||||
|
||||
|
||||
ANCIL_FD_BUFFER(n)
|
||||
int n: number of file descriptors
|
||||
|
||||
Expands to a structure data type large enough to hold the system data
|
||||
structures for n file descriptors. So the address of a variable declared
|
||||
of type ANCIL_FD_BUFFER(n) is suitable as the buffer argument for
|
||||
*_with_buffer on n file descriptors.
|
||||
To use this macro, you need <sys/types.h> and <sys/socket.h>. Bevare: with
|
||||
Solaris, the _XPG4_2 macro must be defined before sys/socket is included.
|
||||
|
||||
|
||||
Tuning the compilation
|
||||
|
||||
This library is designed to be included in projects, not installed in
|
||||
/usr/lib. If your project does not use some of the functions, the
|
||||
TUNE_OPTS variable in the Makefile allows not to build them. It is a list
|
||||
of proprocessor options:
|
||||
|
||||
-DNDEBUG: turn assertions off (see assert(3))
|
||||
-DSPARE_SEND_FDS: do not build ancil_send_fds
|
||||
-DSPARE_SEND_FD: do not build ancil_send_fd
|
||||
-DSPARE_RECV_FDS: do not build ancil_recv_fds
|
||||
-DSPARE_RECV_FD: do not build ancil_recv_fd
|
|
@ -0,0 +1,21 @@
|
|||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. The name of the author may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,73 @@
|
|||
###########################################################################
|
||||
# libancillary - black magic on Unix domain sockets
|
||||
# (C) Nicolas George
|
||||
# Makefile - guess what
|
||||
###########################################################################
|
||||
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
CC=gcc
|
||||
CFLAGS=-Wall -g -O2
|
||||
LDFLAGS=
|
||||
LIBS=
|
||||
AR=ar
|
||||
RANLIB=ranlib
|
||||
RM=rm
|
||||
CP=cp
|
||||
MKDIR=mkdir
|
||||
TAR=tar
|
||||
GZIP=gzip -9
|
||||
|
||||
NAME=libancillary
|
||||
DISTRIBUTION=API COPYING Makefile ancillary.h fd_send.c fd_recv.c test.c
|
||||
VERSION=0.9.1
|
||||
|
||||
OBJECTS=fd_send.o fd_recv.o
|
||||
|
||||
TUNE_OPTS=-DNDEBUG
|
||||
#TUNE_OPTS=-DNDEBUG \
|
||||
-DSPARE_SEND_FDS -DSPARE_SEND_FD -DSPARE_RECV_FDS -DSPARE_RECV_FD
|
||||
|
||||
.c.o:
|
||||
$(CC) -c $(CFLAGS) $(TUNE_OPTS) $<
|
||||
|
||||
all: libancillary.a
|
||||
|
||||
libancillary.a: $(OBJECTS)
|
||||
$(AR) cr $@ $(OBJECTS)
|
||||
$(RANLIB) $@
|
||||
|
||||
fd_send.o: ancillary.h
|
||||
fd_recv.o: ancillary.h
|
||||
|
||||
test: test.c libancillary.a
|
||||
$(CC) -o $@ $(CFLAGS) $(LDFLAGS) -L. test.c -lancillary $(LIBS)
|
||||
|
||||
clean:
|
||||
-$(RM) -f *.o *.a test
|
||||
|
||||
dist:
|
||||
$(MKDIR) $(NAME)-$(VERSION)
|
||||
$(CP) $(DISTRIBUTION) $(NAME)-$(VERSION)
|
||||
$(TAR) -cf - $(NAME)-$(VERSION) | $(GZIP) > $(NAME)-$(VERSION).tar.gz
|
||||
$(RM) -rf $(NAME)-$(VERSION)
|
|
@ -0,0 +1,131 @@
|
|||
/***************************************************************************
|
||||
* libancillary - black magic on Unix domain sockets
|
||||
* (C) Nicolas George
|
||||
* ancillary.c - public header
|
||||
***************************************************************************/
|
||||
|
||||
/*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef ANCILLARY_H__
|
||||
#define ANCILLARY_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/***************************************************************************
|
||||
* Start of the readable part.
|
||||
***************************************************************************/
|
||||
|
||||
#define ANCIL_MAX_N_FDS 960
|
||||
/*
|
||||
* Maximum number of fds that can be sent or received using the "esay"
|
||||
* functions; this is so that all can fit in one page.
|
||||
*/
|
||||
|
||||
extern int
|
||||
ancil_send_fds_with_buffer(int, const int *, unsigned, void *);
|
||||
/*
|
||||
* ancil_send_fds_with_buffer(sock, n_fds, fds, buffer)
|
||||
*
|
||||
* Sends the file descriptors in the array pointed by fds, of length n_fds
|
||||
* on the socket sock.
|
||||
* buffer is a writeable memory area large enough to hold the required data
|
||||
* structures.
|
||||
* Returns: -1 and errno in case of error, 0 in case of success.
|
||||
*/
|
||||
|
||||
extern int
|
||||
ancil_recv_fds_with_buffer(int, int *, unsigned, void *);
|
||||
/*
|
||||
* ancil_recv_fds_with_buffer(sock, n_fds, fds, buffer)
|
||||
*
|
||||
* Receives *n_fds file descriptors into the array pointed by fds
|
||||
* from the socket sock.
|
||||
* buffer is a writeable memory area large enough to hold the required data
|
||||
* structures.
|
||||
* Returns: -1 and errno in case of error, the actual number of received fd
|
||||
* in case of success
|
||||
*/
|
||||
|
||||
#define ANCIL_FD_BUFFER(n) \
|
||||
struct { \
|
||||
struct cmsghdr h; \
|
||||
int fd[n]; \
|
||||
}
|
||||
/* ANCIL_FD_BUFFER(n)
|
||||
*
|
||||
* A structure type suitable to be used as buffer for n file descriptors.
|
||||
* Requires <sys/socket.h>.
|
||||
* Example:
|
||||
* ANCIL_FD_BUFFER(42) buffer;
|
||||
* ancil_recv_fds_with_buffer(sock, 42, my_fds, &buffer);
|
||||
*/
|
||||
|
||||
extern int
|
||||
ancil_send_fds(int, const int *, unsigned);
|
||||
/*
|
||||
* ancil_send_fds(sock, n_fds, fds)
|
||||
*
|
||||
* Sends the file descriptors in the array pointed by fds, of length n_fds
|
||||
* on the socket sock.
|
||||
* n_fds must not be greater than ANCIL_MAX_N_FDS.
|
||||
* Returns: -1 and errno in case of error, 0 in case of success.
|
||||
*/
|
||||
|
||||
extern int
|
||||
ancil_recv_fds(int, int *, unsigned);
|
||||
/*
|
||||
* ancil_recv_fds(sock, n_fds, fds)
|
||||
*
|
||||
* Receives *n_fds file descriptors into the array pointed by fds
|
||||
* from the socket sock.
|
||||
* *n_fds must not be greater than ANCIL_MAX_N_FDS.
|
||||
* Returns: -1 and errno in case of error, the actual number of received fd
|
||||
* in case of success.
|
||||
*/
|
||||
|
||||
|
||||
extern int
|
||||
ancil_send_fd(int, int);
|
||||
/* ancil_recv_fd(sock, fd);
|
||||
*
|
||||
* Sends the file descriptor fd on the socket sock.
|
||||
* Returns : -1 and errno in case of error, 0 in case of success.
|
||||
*/
|
||||
|
||||
extern int
|
||||
ancil_recv_fd(int, int *);
|
||||
/* ancil_send_fd(sock, &fd);
|
||||
*
|
||||
* Receives the file descriptor fd from the socket sock.
|
||||
* Returns : -1 and errno in case of error, 0 in case of success.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ANCILLARY_H__ */
|
|
@ -0,0 +1,98 @@
|
|||
/***************************************************************************
|
||||
* libancillary - black magic on Unix domain sockets
|
||||
* (C) Nicolas George
|
||||
* fd_send.c - receiving file descriptors
|
||||
***************************************************************************/
|
||||
|
||||
/*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _XPG4_2 /* Solaris sucks */
|
||||
# define _XPG4_2
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/uio.h>
|
||||
#include <assert.h>
|
||||
#if defined(__FreeBSD__)
|
||||
# include <sys/param.h> /* FreeBSD sucks */
|
||||
#endif
|
||||
|
||||
#include "ancillary.h"
|
||||
|
||||
int
|
||||
ancil_recv_fds_with_buffer(int sock, int *fds, unsigned n_fds, void *buffer)
|
||||
{
|
||||
struct msghdr msghdr;
|
||||
char nothing;
|
||||
struct iovec nothing_ptr;
|
||||
struct cmsghdr *cmsg;
|
||||
int i;
|
||||
|
||||
nothing_ptr.iov_base = ¬hing;
|
||||
nothing_ptr.iov_len = 1;
|
||||
msghdr.msg_name = NULL;
|
||||
msghdr.msg_namelen = 0;
|
||||
msghdr.msg_iov = ¬hing_ptr;
|
||||
msghdr.msg_iovlen = 1;
|
||||
msghdr.msg_flags = 0;
|
||||
msghdr.msg_control = buffer;
|
||||
msghdr.msg_controllen = sizeof(struct cmsghdr) + sizeof(int) * n_fds;
|
||||
cmsg = CMSG_FIRSTHDR(&msghdr);
|
||||
cmsg->cmsg_len = msghdr.msg_controllen;
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS;
|
||||
for(i = 0; i < n_fds; i++)
|
||||
((int *)CMSG_DATA(cmsg))[i] = -1;
|
||||
|
||||
if(recvmsg(sock, &msghdr, 0) < 0)
|
||||
return(-1);
|
||||
for(i = 0; i < n_fds; i++)
|
||||
fds[i] = ((int *)CMSG_DATA(cmsg))[i];
|
||||
n_fds = (msghdr.msg_controllen - sizeof(struct cmsghdr)) / sizeof(int);
|
||||
return(n_fds);
|
||||
}
|
||||
|
||||
#ifndef SPARE_RECV_FDS
|
||||
int
|
||||
ancil_recv_fds(int sock, int *fd, unsigned n_fds)
|
||||
{
|
||||
ANCIL_FD_BUFFER(ANCIL_MAX_N_FDS) buffer;
|
||||
|
||||
assert(n_fds <= ANCIL_MAX_N_FDS);
|
||||
return(ancil_recv_fds_with_buffer(sock, fd, n_fds, &buffer));
|
||||
}
|
||||
#endif /* SPARE_RECV_FDS */
|
||||
|
||||
#ifndef SPARE_RECV_FD
|
||||
int
|
||||
ancil_recv_fd(int sock, int *fd)
|
||||
{
|
||||
ANCIL_FD_BUFFER(1) buffer;
|
||||
|
||||
return(ancil_recv_fds_with_buffer(sock, fd, 1, &buffer) == 1 ? 0 : -1);
|
||||
}
|
||||
#endif /* SPARE_RECV_FD */
|
|
@ -0,0 +1,92 @@
|
|||
/***************************************************************************
|
||||
* libancillary - black magic on Unix domain sockets
|
||||
* (C) Nicolas George
|
||||
* fd_send.c - sending file descriptors
|
||||
***************************************************************************/
|
||||
|
||||
/*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _XPG4_2 /* Solaris sucks */
|
||||
# define _XPG4_2
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/uio.h>
|
||||
#include <assert.h>
|
||||
#if defined(__FreeBSD__)
|
||||
# include <sys/param.h> /* FreeBSD sucks */
|
||||
#endif
|
||||
|
||||
#include "ancillary.h"
|
||||
|
||||
int
|
||||
ancil_send_fds_with_buffer(int sock, const int *fds, unsigned n_fds, void *buffer)
|
||||
{
|
||||
struct msghdr msghdr;
|
||||
char nothing = '!';
|
||||
struct iovec nothing_ptr;
|
||||
struct cmsghdr *cmsg;
|
||||
int i;
|
||||
|
||||
nothing_ptr.iov_base = ¬hing;
|
||||
nothing_ptr.iov_len = 1;
|
||||
msghdr.msg_name = NULL;
|
||||
msghdr.msg_namelen = 0;
|
||||
msghdr.msg_iov = ¬hing_ptr;
|
||||
msghdr.msg_iovlen = 1;
|
||||
msghdr.msg_flags = 0;
|
||||
msghdr.msg_control = buffer;
|
||||
msghdr.msg_controllen = sizeof(struct cmsghdr) + sizeof(int) * n_fds;
|
||||
cmsg = CMSG_FIRSTHDR(&msghdr);
|
||||
cmsg->cmsg_len = msghdr.msg_controllen;
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS;
|
||||
for(i = 0; i < n_fds; i++)
|
||||
((int *)CMSG_DATA(cmsg))[i] = fds[i];
|
||||
return(sendmsg(sock, &msghdr, 0) >= 0 ? 0 : -1);
|
||||
}
|
||||
|
||||
#ifndef SPARE_SEND_FDS
|
||||
int
|
||||
ancil_send_fds(int sock, const int *fds, unsigned n_fds)
|
||||
{
|
||||
ANCIL_FD_BUFFER(ANCIL_MAX_N_FDS) buffer;
|
||||
|
||||
assert(n_fds <= ANCIL_MAX_N_FDS);
|
||||
return(ancil_send_fds_with_buffer(sock, fds, n_fds, &buffer));
|
||||
}
|
||||
#endif /* SPARE_SEND_FDS */
|
||||
|
||||
#ifndef SPARE_SEND_FD
|
||||
int
|
||||
ancil_send_fd(int sock, int fd)
|
||||
{
|
||||
ANCIL_FD_BUFFER(1) buffer;
|
||||
|
||||
return(ancil_send_fds_with_buffer(sock, &fd, 1, &buffer));
|
||||
}
|
||||
#endif /* SPARE_SEND_FD */
|
|
@ -0,0 +1,112 @@
|
|||
/***************************************************************************
|
||||
* libancillary - black magic on Unix domain sockets
|
||||
* (C) Nicolas George
|
||||
* test.c - testing and example program
|
||||
***************************************************************************/
|
||||
|
||||
/*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/socket.h>
|
||||
#include "ancillary.h"
|
||||
|
||||
void child_process(int sock)
|
||||
{
|
||||
int fd;
|
||||
int fds[3], nfds;
|
||||
char b[] = "This is on the received fd!\n";
|
||||
|
||||
if(ancil_recv_fd(sock, &fd)) {
|
||||
perror("ancil_recv_fd");
|
||||
exit(1);
|
||||
} else {
|
||||
printf("Received fd: %d\n", fd);
|
||||
}
|
||||
write(fd, b, sizeof(b));
|
||||
close(fd);
|
||||
sleep(2);
|
||||
|
||||
nfds = ancil_recv_fds(sock, fds, 3);
|
||||
if(nfds < 0) {
|
||||
perror("ancil_recv_fds");
|
||||
exit(1);
|
||||
} else {
|
||||
printf("Received %d/3 fds : %d %d %d.\n", nfds,
|
||||
fds[0], fds[1], fds[2]);
|
||||
}
|
||||
}
|
||||
|
||||
void parent_process(int sock)
|
||||
{
|
||||
int fds[2] = { 1, 2 };
|
||||
|
||||
if(ancil_send_fd(sock, 1)) {
|
||||
perror("ancil_send_fd");
|
||||
exit(1);
|
||||
} else {
|
||||
printf("Sent fd.\n");
|
||||
}
|
||||
sleep(1);
|
||||
|
||||
if(ancil_send_fds(sock, fds, 2)) {
|
||||
perror("ancil_send_fds");
|
||||
exit(1);
|
||||
} else {
|
||||
printf("Sent two fds.\n");
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int sock[2];
|
||||
|
||||
if(socketpair(PF_UNIX, SOCK_STREAM, 0, sock)) {
|
||||
perror("socketpair");
|
||||
exit(1);
|
||||
} else {
|
||||
printf("Established socket pair: (%d, %d)\n", sock[0], sock[1]);
|
||||
}
|
||||
|
||||
switch(fork()) {
|
||||
case 0:
|
||||
close(sock[0]);
|
||||
child_process(sock[1]);
|
||||
break;
|
||||
case -1:
|
||||
perror("fork");
|
||||
exit(1);
|
||||
default:
|
||||
close(sock[1]);
|
||||
parent_process(sock[0]);
|
||||
wait(NULL);
|
||||
break;
|
||||
}
|
||||
return(0);
|
||||
}
|
After Width: | Height: | Size: 480 B |
After Width: | Height: | Size: 802 B |
After Width: | Height: | Size: 744 B |
After Width: | Height: | Size: 967 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 616 B |
After Width: | Height: | Size: 713 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 553 B |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 852 B |
After Width: | Height: | Size: 990 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 294 B |
After Width: | Height: | Size: 390 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 342 B |
After Width: | Height: | Size: 358 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 319 B |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 647 B |
After Width: | Height: | Size: 606 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 357 B |
After Width: | Height: | Size: 517 B |
After Width: | Height: | Size: 607 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 458 B |
After Width: | Height: | Size: 475 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 364 B |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 883 B |
After Width: | Height: | Size: 535 B |
After Width: | Height: | Size: 600 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 618 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 747 B |
After Width: | Height: | Size: 970 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 716 B |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true" >
|
||||
<shape>
|
||||
<solid
|
||||
android:color="@color/panel_background" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#ffffff" />
|
||||
<corners
|
||||
android:radius="6dp" />
|
||||
<padding
|
||||
android:left="10dp"
|
||||
android:top="10dp"
|
||||
android:right="10dp"
|
||||
android:bottom="10dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<gradient
|
||||
android:startColor="@color/panel_background_dark"
|
||||
android:endColor="@color/panel_background_main"
|
||||
android:angle="270" />
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="#aaaaaa" />
|
||||
<corners
|
||||
android:radius="6dp" />
|
||||
<padding
|
||||
android:left="10dp"
|
||||
android:top="10dp"
|
||||
android:right="10dp"
|
||||
android:bottom="10dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true" >
|
||||
<shape>
|
||||
<solid
|
||||
android:color="@color/panel_background" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#ffffff" />
|
||||
<corners
|
||||
android:radius="6dp" />
|
||||
<padding
|
||||
android:left="10dp"
|
||||
android:top="10dp"
|
||||
android:right="10dp"
|
||||
android:bottom="10dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<gradient
|
||||
android:startColor="@color/panel_background_dark"
|
||||
android:endColor="@color/panel_background_main"
|
||||
android:angle="270" />
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="#555555" />
|
||||
<corners
|
||||
android:radius="6dp" />
|
||||
<padding
|
||||
android:left="10dp"
|
||||
android:top="10dp"
|
||||
android:right="10dp"
|
||||
android:bottom="10dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true" >
|
||||
<shape>
|
||||
<solid
|
||||
android:color="@color/bright_green" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#ffffff" />
|
||||
<corners
|
||||
android:radius="6dp" />
|
||||
<padding
|
||||
android:left="10dp"
|
||||
android:top="10dp"
|
||||
android:right="10dp"
|
||||
android:bottom="10dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<solid
|
||||
android:color="@color/dark_green" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#ffffff" />
|
||||
<corners
|
||||
android:radius="6dp" />
|
||||
<padding
|
||||
android:left="10dp"
|
||||
android:top="10dp"
|
||||
android:right="10dp"
|
||||
android:bottom="10dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 4.9 KiB |