After Width: | Height: | Size: 223 B |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 525 B |
After Width: | Height: | Size: 774 B |
After Width: | Height: | Size: 174 B |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 430 B |
After Width: | Height: | Size: 625 B |
After Width: | Height: | Size: 198 B |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 727 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 222 B |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 397 B |
After Width: | Height: | Size: 379 B |
After Width: | Height: | Size: 269 B |
After Width: | Height: | Size: 9.0 KiB |
@ -0,0 +1,52 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<FrameLayout android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.camera.CameraView
|
||||||
|
android:id="@+id/scanner"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:camera="0"/>
|
||||||
|
|
||||||
|
<LinearLayout android:id="@+id/overlay"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:weightSum="2">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.ShapeScrim
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:background="@color/gray5"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<ImageView android:id="@+id/devices"
|
||||||
|
android:src="@drawable/ic_devices_white"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:tint="@color/gray27"
|
||||||
|
android:transitionName="devices"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<TextView android:text="@string/device_add_fragment__scan_the_qr_code_displayed_on_the_device_to_link"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_devices_white"
|
||||||
|
android:transitionName="devices"
|
||||||
|
android:tint="@color/gray27"
|
||||||
|
android:layout_marginBottom="25dp"
|
||||||
|
android:contentDescription="@string/device_link_fragment__link_device"/>
|
||||||
|
|
||||||
|
<android.support.v7.widget.CardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="25dp"
|
||||||
|
android:layout_marginRight="25dp">
|
||||||
|
|
||||||
|
<LinearLayout android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:text="@string/DeviceProvisioningActivity_link_this_device"
|
||||||
|
android:textSize="16sp"/>
|
||||||
|
|
||||||
|
<View android:layout_width="match_parent"
|
||||||
|
android:layout_height="0.5dp"
|
||||||
|
android:background="#1E000000"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:text="@string/DeviceProvisioningActivity_content_intro"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:text="@string/DeviceProvisioningActivity_content_bullets"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<View android:layout_width="match_parent"
|
||||||
|
android:layout_height="0.5dp"
|
||||||
|
android:background="#1E000000"/>
|
||||||
|
|
||||||
|
<LinearLayout android:id="@+id/link_device"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:clickable="true">
|
||||||
|
|
||||||
|
<ImageView android:id="@+id/check"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:src="@drawable/ic_check_white_24dp"
|
||||||
|
android:tint="@color/blue_400"
|
||||||
|
android:clickable="false"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="@color/blue_400"
|
||||||
|
android:text="@string/device_link_fragment__link_device"
|
||||||
|
android:clickable="false"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</android.support.v7.widget.CardView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<changeTransform />
|
||||||
|
<changeBounds />
|
||||||
|
</transitionSet>
|
@ -0,0 +1,194 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.transition.TransitionInflater;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
|
import org.thoughtcrime.securesms.push.TextSecureCommunicationFactory;
|
||||||
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
|
import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
||||||
|
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||||
|
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||||
|
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||||
|
import org.whispersystems.textsecure.api.TextSecureAccountManager;
|
||||||
|
import org.whispersystems.textsecure.api.push.exceptions.NotFoundException;
|
||||||
|
import org.whispersystems.textsecure.internal.push.DeviceLimitExceededException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class DeviceActivity extends PassphraseRequiredActionBarActivity
|
||||||
|
implements Button.OnClickListener, DeviceAddFragment.ScanListener, DeviceLinkFragment.LinkClickedListener
|
||||||
|
{
|
||||||
|
|
||||||
|
private static final String TAG = DeviceActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
||||||
|
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||||
|
|
||||||
|
private DeviceAddFragment deviceAddFragment;
|
||||||
|
private DeviceListFragment deviceListFragment;
|
||||||
|
private DeviceLinkFragment deviceLinkFragment;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPreCreate() {
|
||||||
|
dynamicTheme.onCreate(this);
|
||||||
|
dynamicLanguage.onCreate(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle bundle, @NonNull MasterSecret masterSecret) {
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
this.deviceAddFragment = new DeviceAddFragment();
|
||||||
|
this.deviceListFragment = new DeviceListFragment();
|
||||||
|
this.deviceLinkFragment = new DeviceLinkFragment();
|
||||||
|
|
||||||
|
this.deviceListFragment.setAddDeviceButtonListener(this);
|
||||||
|
this.deviceAddFragment.setScanListener(this);
|
||||||
|
|
||||||
|
initFragment(android.R.id.content, deviceListFragment, masterSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
dynamicTheme.onResume(this);
|
||||||
|
dynamicLanguage.onResume(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home: finish(); return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.replace(android.R.id.content, deviceAddFragment)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUrlFound(final Uri uri) {
|
||||||
|
Util.runOnMain(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
|
||||||
|
deviceLinkFragment.setLinkClickedListener(uri, DeviceActivity.this);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
deviceAddFragment.setSharedElementReturnTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared));
|
||||||
|
deviceAddFragment.setExitTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade));
|
||||||
|
|
||||||
|
deviceLinkFragment.setSharedElementEnterTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared));
|
||||||
|
deviceLinkFragment.setEnterTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade));
|
||||||
|
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.addToBackStack(null)
|
||||||
|
.addSharedElement(deviceAddFragment.getDevicesImage(), "devices")
|
||||||
|
.replace(android.R.id.content, deviceLinkFragment)
|
||||||
|
.commit();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.setCustomAnimations(R.anim.slide_from_bottom, R.anim.slide_to_bottom,
|
||||||
|
R.anim.slide_from_bottom, R.anim.slide_to_bottom)
|
||||||
|
.replace(android.R.id.content, deviceLinkFragment)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLink(final Uri uri) {
|
||||||
|
new ProgressDialogAsyncTask<Void, Void, Integer>(this,
|
||||||
|
R.string.DeviceProvisioningActivity_content_progress_title,
|
||||||
|
R.string.DeviceProvisioningActivity_content_progress_content)
|
||||||
|
{
|
||||||
|
private static final int SUCCESS = 0;
|
||||||
|
private static final int NO_DEVICE = 1;
|
||||||
|
private static final int NETWORK_ERROR = 2;
|
||||||
|
private static final int KEY_ERROR = 3;
|
||||||
|
private static final int LIMIT_EXCEEDED = 4;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Integer doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
Context context = DeviceActivity.this;
|
||||||
|
TextSecureAccountManager accountManager = TextSecureCommunicationFactory.createManager(context);
|
||||||
|
String verificationCode = accountManager.getNewDeviceVerificationCode();
|
||||||
|
String ephemeralId = uri.getQueryParameter("uuid");
|
||||||
|
String publicKeyEncoded = uri.getQueryParameter("pub_key");
|
||||||
|
ECPublicKey publicKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
|
||||||
|
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context);
|
||||||
|
|
||||||
|
accountManager.addDevice(ephemeralId, publicKey, identityKeyPair, verificationCode);
|
||||||
|
TextSecurePreferences.setMultiDevice(context, true);
|
||||||
|
return SUCCESS;
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
return NO_DEVICE;
|
||||||
|
} catch (DeviceLimitExceededException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
return LIMIT_EXCEEDED;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
return NETWORK_ERROR;
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
return KEY_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Integer result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
|
||||||
|
Context context = DeviceActivity.this;
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case SUCCESS:
|
||||||
|
Toast.makeText(context, R.string.DeviceProvisioningActivity_content_progress_success, Toast.LENGTH_SHORT).show();
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
case NO_DEVICE:
|
||||||
|
Toast.makeText(context, R.string.DeviceProvisioningActivity_content_progress_no_device, Toast.LENGTH_LONG).show();
|
||||||
|
break;
|
||||||
|
case NETWORK_ERROR:
|
||||||
|
Toast.makeText(context, R.string.DeviceProvisioningActivity_content_progress_network_error, Toast.LENGTH_LONG).show();
|
||||||
|
break;
|
||||||
|
case KEY_ERROR:
|
||||||
|
Toast.makeText(context, R.string.DeviceProvisioningActivity_content_progress_key_error, Toast.LENGTH_LONG).show();
|
||||||
|
break;
|
||||||
|
case LIMIT_EXCEEDED:
|
||||||
|
Toast.makeText(context, R.string.DeviceProvisioningActivity_sorry_you_have_too_many_devices_linked_already, Toast.LENGTH_LONG).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSupportFragmentManager().popBackStackImmediate();
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,220 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewAnimationUtils;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.animation.DecelerateInterpolator;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import com.google.zxing.BinaryBitmap;
|
||||||
|
import com.google.zxing.ChecksumException;
|
||||||
|
import com.google.zxing.FormatException;
|
||||||
|
import com.google.zxing.NotFoundException;
|
||||||
|
import com.google.zxing.PlanarYUVLuminanceSource;
|
||||||
|
import com.google.zxing.Result;
|
||||||
|
import com.google.zxing.common.HybridBinarizer;
|
||||||
|
import com.google.zxing.qrcode.QRCodeReader;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.components.camera.CameraView;
|
||||||
|
import org.thoughtcrime.securesms.components.camera.CameraView.PreviewCallback;
|
||||||
|
import org.thoughtcrime.securesms.components.camera.CameraView.PreviewFrame;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
|
public class DeviceAddFragment extends Fragment implements PreviewCallback {
|
||||||
|
|
||||||
|
private static final String TAG = DeviceAddFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
private final QRCodeReader reader = new QRCodeReader();
|
||||||
|
|
||||||
|
private ViewGroup container;
|
||||||
|
private LinearLayout overlay;
|
||||||
|
private ImageView devicesImage;
|
||||||
|
private CameraView scannerView;
|
||||||
|
private PreviewFrame previewFrame;
|
||||||
|
private ScanningThread scanningThread;
|
||||||
|
private ScanListener scanListener;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
|
||||||
|
this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.device_add_fragment);
|
||||||
|
this.overlay = ViewUtil.findById(this.container, R.id.overlay);
|
||||||
|
this.scannerView = ViewUtil.findById(this.container, R.id.scanner);
|
||||||
|
this.devicesImage = ViewUtil.findById(this.container, R.id.devices);
|
||||||
|
this.scannerView.onResume();
|
||||||
|
this.scannerView.setPreviewCallback(this);
|
||||||
|
|
||||||
|
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
|
this.overlay.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
} else {
|
||||||
|
this.overlay.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
this.container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
@Override
|
||||||
|
public void onLayoutChange(View v, int left, int top, int right, int bottom,
|
||||||
|
int oldLeft, int oldTop, int oldRight, int oldBottom)
|
||||||
|
{
|
||||||
|
v.removeOnLayoutChangeListener(this);
|
||||||
|
|
||||||
|
Animator reveal = ViewAnimationUtils.createCircularReveal(v, right, bottom, 0, (int) Math.hypot(right, bottom));
|
||||||
|
reveal.setInterpolator(new DecelerateInterpolator(2f));
|
||||||
|
reveal.setDuration(800);
|
||||||
|
reveal.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.container;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
this.scannerView.onResume();
|
||||||
|
this.scannerView.setPreviewCallback(this);
|
||||||
|
this.previewFrame = null;
|
||||||
|
this.scanningThread = new ScanningThread();
|
||||||
|
this.scanningThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
this.scannerView.onPause();
|
||||||
|
this.scanningThread.stopScanning();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration newConfiguration) {
|
||||||
|
super.onConfigurationChanged(newConfiguration);
|
||||||
|
|
||||||
|
this.scannerView.onPause();
|
||||||
|
|
||||||
|
if (newConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
|
overlay.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
} else {
|
||||||
|
overlay.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scannerView.onResume();
|
||||||
|
this.scannerView.setPreviewCallback(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPreviewFrame(@NonNull PreviewFrame previewFrame) {
|
||||||
|
Context context = getActivity();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (context != null) {
|
||||||
|
synchronized (this) {
|
||||||
|
this.previewFrame = previewFrame;
|
||||||
|
this.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageView getDevicesImage() {
|
||||||
|
return devicesImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScanListener(ScanListener scanListener) {
|
||||||
|
this.scanListener = scanListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ScanningThread extends Thread {
|
||||||
|
|
||||||
|
private boolean scanning = true;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (true) {
|
||||||
|
PreviewFrame ourFrame;
|
||||||
|
|
||||||
|
synchronized (DeviceAddFragment.this) {
|
||||||
|
while (scanning && previewFrame == null) {
|
||||||
|
Util.wait(DeviceAddFragment.this, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scanning) return;
|
||||||
|
else ourFrame = previewFrame;
|
||||||
|
|
||||||
|
previewFrame = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = getUrl(ourFrame.getData(), ourFrame.getWidth(), ourFrame.getHeight(), ourFrame.getOrientation());
|
||||||
|
|
||||||
|
if (url != null && scanListener != null) {
|
||||||
|
Uri uri = Uri.parse(url);
|
||||||
|
scanListener.onUrlFound(uri);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopScanning() {
|
||||||
|
synchronized (DeviceAddFragment.this) {
|
||||||
|
scanning = false;
|
||||||
|
DeviceAddFragment.this.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable String getUrl(byte[] data, int width, int height, int orientation) {
|
||||||
|
try {
|
||||||
|
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||||
|
byte[] rotatedData = new byte[data.length];
|
||||||
|
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
rotatedData[x * height + height - y - 1] = data[x + y * width];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int tmp = width;
|
||||||
|
width = height;
|
||||||
|
height = tmp;
|
||||||
|
data = rotatedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(data, width, height,
|
||||||
|
0, 0, width, height,
|
||||||
|
false);
|
||||||
|
|
||||||
|
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||||
|
|
||||||
|
Result result = reader.decode(bitmap);
|
||||||
|
|
||||||
|
if (result != null) return result.getText();
|
||||||
|
|
||||||
|
} catch (NullPointerException | ChecksumException | FormatException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
// Thanks ZXing...
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ScanListener {
|
||||||
|
public void onUrlFound(Uri uri);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
public class DeviceLinkFragment extends Fragment implements View.OnClickListener {
|
||||||
|
|
||||||
|
private LinearLayout container;
|
||||||
|
private LinkClickedListener linkClickedListener;
|
||||||
|
private Uri uri;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
|
||||||
|
this.container = (LinearLayout) inflater.inflate(R.layout.device_link_fragment, container, false);
|
||||||
|
this.container.findViewById(R.id.link_device).setOnClickListener(this);
|
||||||
|
|
||||||
|
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
|
container.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
} else {
|
||||||
|
container.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.container;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration newConfiguration) {
|
||||||
|
if (newConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
|
container.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
} else {
|
||||||
|
container.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLinkClickedListener(Uri uri, LinkClickedListener linkClickedListener) {
|
||||||
|
this.uri = uri;
|
||||||
|
this.linkClickedListener = linkClickedListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (linkClickedListener != null) {
|
||||||
|
linkClickedListener.onLink(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface LinkClickedListener {
|
||||||
|
public void onLink(Uri uri);
|
||||||
|
}
|
||||||
|
}
|
@ -1,215 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.v4.app.ListFragment;
|
|
||||||
import android.support.v4.app.LoaderManager;
|
|
||||||
import android.support.v4.content.Loader;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|
||||||
import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
|
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
|
||||||
import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
import org.whispersystems.textsecure.api.TextSecureAccountManager;
|
|
||||||
import org.whispersystems.textsecure.api.messages.multidevice.DeviceInfo;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
public class DeviceListActivity extends PassphraseRequiredActionBarActivity {
|
|
||||||
|
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
|
||||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPreCreate() {
|
|
||||||
dynamicTheme.onCreate(this);
|
|
||||||
dynamicLanguage.onCreate(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle bundle, @NonNull MasterSecret masterSecret) {
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
initFragment(android.R.id.content, new DeviceListFragment(), masterSecret);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
dynamicTheme.onResume(this);
|
|
||||||
dynamicLanguage.onResume(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case android.R.id.home: finish(); return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DeviceListFragment extends ListFragment
|
|
||||||
implements LoaderManager.LoaderCallbacks<List<DeviceInfo>>, ListView.OnItemClickListener, InjectableType
|
|
||||||
{
|
|
||||||
|
|
||||||
private static final String TAG = DeviceListFragment.class.getSimpleName();
|
|
||||||
|
|
||||||
@Inject TextSecureAccountManager accountManager;
|
|
||||||
|
|
||||||
private View empty;
|
|
||||||
private View progressContainer;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Activity activity) {
|
|
||||||
super.onAttach(activity);
|
|
||||||
ApplicationContext.getInstance(activity).injectDependencies(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
|
||||||
View view = inflater.inflate(R.layout.device_list_fragment, container, false);
|
|
||||||
|
|
||||||
this.empty = view.findViewById(R.id.empty);
|
|
||||||
this.progressContainer = view.findViewById(R.id.progress_container);
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(Bundle bundle) {
|
|
||||||
super.onActivityCreated(bundle);
|
|
||||||
getLoaderManager().initLoader(0, null, this).forceLoad();
|
|
||||||
getListView().setOnItemClickListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Loader<List<DeviceInfo>> onCreateLoader(int id, Bundle args) {
|
|
||||||
empty.setVisibility(View.GONE);
|
|
||||||
progressContainer.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
return new DeviceListLoader(getActivity(), accountManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader<List<DeviceInfo>> loader, List<DeviceInfo> data) {
|
|
||||||
progressContainer.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
if (data == null) {
|
|
||||||
handleLoaderFailed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setListAdapter(new DeviceListAdapter(getActivity(), R.layout.device_list_item_view, data));
|
|
||||||
|
|
||||||
if (data.isEmpty()) {
|
|
||||||
empty.setVisibility(View.VISIBLE);
|
|
||||||
TextSecurePreferences.setMultiDevice(getActivity(), false);
|
|
||||||
} else {
|
|
||||||
empty.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader<List<DeviceInfo>> loader) {
|
|
||||||
setListAdapter(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
final String deviceName = ((DeviceListItem)view).getDeviceName();
|
|
||||||
final long deviceId = ((DeviceListItem)view).getDeviceId();
|
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
|
||||||
builder.setTitle(getActivity().getString(R.string.DeviceListActivity_unlink_s, deviceName));
|
|
||||||
builder.setMessage(R.string.DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive);
|
|
||||||
builder.setNegativeButton(android.R.string.cancel, null);
|
|
||||||
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
handleDisconnectDevice(deviceId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
builder.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleLoaderFailed() {
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
|
||||||
builder.setMessage(R.string.DeviceListActivity_network_connection_failed);
|
|
||||||
builder.setPositiveButton(R.string.DeviceListActivity_try_again,
|
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
getLoaderManager().initLoader(0, null, DeviceListFragment.this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
builder.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleDisconnectDevice(final long deviceId) {
|
|
||||||
new ProgressDialogAsyncTask<Void, Void, Void>(getActivity(),
|
|
||||||
R.string.DeviceListActivity_unlinking_device_no_ellipsis,
|
|
||||||
R.string.DeviceListActivity_unlinking_device)
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... params) {
|
|
||||||
try {
|
|
||||||
accountManager.removeDevice(deviceId);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
Toast.makeText(getActivity(), R.string.DeviceListActivity_network_failed, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void result) {
|
|
||||||
super.onPostExecute(result);
|
|
||||||
getLoaderManager().restartLoader(0, null, DeviceListFragment.this);
|
|
||||||
}
|
|
||||||
}.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class DeviceListAdapter extends ArrayAdapter<DeviceInfo> {
|
|
||||||
|
|
||||||
private final int resource;
|
|
||||||
|
|
||||||
public DeviceListAdapter(Context context, int resource, List<DeviceInfo> objects) {
|
|
||||||
super(context, resource, objects);
|
|
||||||
this.resource = resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
|
||||||
if (convertView == null) {
|
|
||||||
convertView = ((Activity)getContext()).getLayoutInflater().inflate(resource, parent, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
((DeviceListItem)convertView).set(getItem(position));
|
|
||||||
|
|
||||||
return convertView;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,192 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.ListFragment;
|
||||||
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.melnykov.fab.FloatingActionButton;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||||
|
import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
import org.whispersystems.textsecure.api.TextSecureAccountManager;
|
||||||
|
import org.whispersystems.textsecure.api.messages.multidevice.DeviceInfo;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
public class DeviceListFragment extends ListFragment
|
||||||
|
implements LoaderManager.LoaderCallbacks<List<DeviceInfo>>,
|
||||||
|
ListView.OnItemClickListener, InjectableType, Button.OnClickListener
|
||||||
|
{
|
||||||
|
|
||||||
|
private static final String TAG = DeviceListFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
TextSecureAccountManager accountManager;
|
||||||
|
|
||||||
|
private View empty;
|
||||||
|
private View progressContainer;
|
||||||
|
private FloatingActionButton addDeviceButton;
|
||||||
|
private Button.OnClickListener addDeviceButtonListener;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
ApplicationContext.getInstance(activity).injectDependencies(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||||
|
View view = inflater.inflate(R.layout.device_list_fragment, container, false);
|
||||||
|
|
||||||
|
this.empty = view.findViewById(R.id.empty);
|
||||||
|
this.progressContainer = view.findViewById(R.id.progress_container);
|
||||||
|
this.addDeviceButton = ViewUtil.findById(view, R.id.add_device);
|
||||||
|
this.addDeviceButton.setOnClickListener(this);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle bundle) {
|
||||||
|
super.onActivityCreated(bundle);
|
||||||
|
getLoaderManager().initLoader(0, null, this).forceLoad();
|
||||||
|
getListView().setOnItemClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddDeviceButtonListener(Button.OnClickListener listener) {
|
||||||
|
this.addDeviceButtonListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Loader<List<DeviceInfo>> onCreateLoader(int id, Bundle args) {
|
||||||
|
empty.setVisibility(View.GONE);
|
||||||
|
progressContainer.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
return new DeviceListLoader(getActivity(), accountManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<List<DeviceInfo>> loader, List<DeviceInfo> data) {
|
||||||
|
progressContainer.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
if (data == null) {
|
||||||
|
handleLoaderFailed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setListAdapter(new DeviceListAdapter(getActivity(), R.layout.device_list_item_view, data));
|
||||||
|
|
||||||
|
if (data.isEmpty()) {
|
||||||
|
empty.setVisibility(View.VISIBLE);
|
||||||
|
TextSecurePreferences.setMultiDevice(getActivity(), false);
|
||||||
|
} else {
|
||||||
|
empty.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<List<DeviceInfo>> loader) {
|
||||||
|
setListAdapter(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
final String deviceName = ((DeviceListItem)view).getDeviceName();
|
||||||
|
final long deviceId = ((DeviceListItem)view).getDeviceId();
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
builder.setTitle(getActivity().getString(R.string.DeviceListActivity_unlink_s, deviceName));
|
||||||
|
builder.setMessage(R.string.DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive);
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
|
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
handleDisconnectDevice(deviceId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleLoaderFailed() {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
builder.setMessage(R.string.DeviceListActivity_network_connection_failed);
|
||||||
|
builder.setPositiveButton(R.string.DeviceListActivity_try_again,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
getLoaderManager().initLoader(0, null, DeviceListFragment.this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDisconnectDevice(final long deviceId) {
|
||||||
|
new ProgressDialogAsyncTask<Void, Void, Void>(getActivity(),
|
||||||
|
R.string.DeviceListActivity_unlinking_device_no_ellipsis,
|
||||||
|
R.string.DeviceListActivity_unlinking_device)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
accountManager.removeDevice(deviceId);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
Toast.makeText(getActivity(), R.string.DeviceListActivity_network_failed, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
getLoaderManager().restartLoader(0, null, DeviceListFragment.this);
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (addDeviceButtonListener != null) addDeviceButtonListener.onClick(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DeviceListAdapter extends ArrayAdapter<DeviceInfo> {
|
||||||
|
|
||||||
|
private final int resource;
|
||||||
|
|
||||||
|
public DeviceListAdapter(Context context, int resource, List<DeviceInfo> objects) {
|
||||||
|
super(context, resource, objects);
|
||||||
|
this.resource = resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
if (convertView == null) {
|
||||||
|
convertView = ((Activity)getContext()).getLayoutInflater().inflate(resource, parent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
((DeviceListItem)convertView).set(getItem(position));
|
||||||
|
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
package org.thoughtcrime.securesms.components;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.PorterDuffXfermode;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
|
||||||
|
public class ShapeScrim extends View {
|
||||||
|
|
||||||
|
private enum ShapeType {
|
||||||
|
CIRCLE, SQUARE
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Paint eraser;
|
||||||
|
private final ShapeType shape;
|
||||||
|
private final float radius;
|
||||||
|
|
||||||
|
private Bitmap scrim;
|
||||||
|
private Canvas scrimCanvas;
|
||||||
|
|
||||||
|
public ShapeScrim(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShapeScrim(Context context, AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShapeScrim(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
|
||||||
|
if (attrs != null) {
|
||||||
|
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ShapeScrim, 0, 0);
|
||||||
|
String shapeName = typedArray.getString(R.styleable.ShapeScrim_shape);
|
||||||
|
|
||||||
|
if ("square".equalsIgnoreCase(shapeName)) this.shape = ShapeType.SQUARE;
|
||||||
|
else if ("circle".equalsIgnoreCase(shapeName)) this.shape = ShapeType.CIRCLE;
|
||||||
|
else this.shape = ShapeType.SQUARE;
|
||||||
|
|
||||||
|
this.radius = typedArray.getFloat(R.styleable.ShapeScrim_radius, 0.4f);
|
||||||
|
|
||||||
|
typedArray.recycle();
|
||||||
|
} else {
|
||||||
|
this.shape = ShapeType.SQUARE;
|
||||||
|
this.radius = 0.4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eraser = new Paint();
|
||||||
|
this.eraser.setColor(0xFFFFFFFF);
|
||||||
|
this.eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDraw(Canvas canvas) {
|
||||||
|
super.onDraw(canvas);
|
||||||
|
|
||||||
|
int shortDimension = getWidth() < getHeight() ? getWidth() : getHeight();
|
||||||
|
float drawRadius = shortDimension * radius;
|
||||||
|
|
||||||
|
if (scrimCanvas == null) {
|
||||||
|
scrim = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
|
||||||
|
scrimCanvas = new Canvas(scrim);
|
||||||
|
}
|
||||||
|
|
||||||
|
scrim.eraseColor(Color.TRANSPARENT);
|
||||||
|
scrimCanvas.drawColor(Color.parseColor("#55BDBDBD"));
|
||||||
|
|
||||||
|
if (shape == ShapeType.CIRCLE) drawCircle(scrimCanvas, drawRadius, eraser);
|
||||||
|
else drawSquare(scrimCanvas, drawRadius, eraser);
|
||||||
|
|
||||||
|
canvas.drawBitmap(scrim, 0, 0, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
|
||||||
|
super.onSizeChanged(width, height, oldHeight, oldHeight);
|
||||||
|
|
||||||
|
if (width != oldWidth || height != oldHeight) {
|
||||||
|
scrim = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||||
|
scrimCanvas = new Canvas(scrim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawCircle(Canvas canvas, float radius, Paint eraser) {
|
||||||
|
canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, eraser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawSquare(Canvas canvas, float radius, Paint eraser) {
|
||||||
|
float left = (getWidth() / 2 ) - radius;
|
||||||
|
float top = (getHeight() / 2) - radius;
|
||||||
|
float right = left + (radius * 2);
|
||||||
|
float bottom = top + (radius * 2);
|
||||||
|
|
||||||
|
RectF square = new RectF(left, top, right, bottom);
|
||||||
|
|
||||||
|
canvas.drawRoundRect(square, 25, 25, eraser);
|
||||||
|
}
|
||||||
|
}
|