diff options
Diffstat (limited to 'ui/src/main/java/com/wireguard/android/QuickTileService.java')
-rw-r--r-- | ui/src/main/java/com/wireguard/android/QuickTileService.java | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/ui/src/main/java/com/wireguard/android/QuickTileService.java b/ui/src/main/java/com/wireguard/android/QuickTileService.java new file mode 100644 index 00000000..66aecec3 --- /dev/null +++ b/ui/src/main/java/com/wireguard/android/QuickTileService.java @@ -0,0 +1,174 @@ +/* + * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.android; + +import android.content.Intent; +import androidx.databinding.Observable; +import androidx.databinding.Observable.OnPropertyChangedCallback; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Icon; +import android.os.Build; +import android.os.IBinder; +import android.service.quicksettings.Tile; +import android.service.quicksettings.TileService; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import android.util.Log; + +import com.wireguard.android.activity.MainActivity; +import com.wireguard.android.activity.TunnelToggleActivity; +import com.wireguard.android.backend.Tunnel.State; +import com.wireguard.android.model.ObservableTunnel; +import com.wireguard.android.widget.SlashDrawable; + +import java.util.Objects; + +/** + * Service that maintains the application's custom Quick Settings tile. This service is bound by the + * system framework as necessary to update the appearance of the tile in the system UI, and to + * forward click events to the application. + */ + +@RequiresApi(Build.VERSION_CODES.N) +public class QuickTileService extends TileService { + private static final String TAG = "WireGuard/" + QuickTileService.class.getSimpleName(); + + private final OnStateChangedCallback onStateChangedCallback = new OnStateChangedCallback(); + private final OnTunnelChangedCallback onTunnelChangedCallback = new OnTunnelChangedCallback(); + @Nullable private Icon iconOff; + @Nullable private Icon iconOn; + @Nullable private ObservableTunnel tunnel; + + /* This works around an annoying unsolved frameworks bug some people are hitting. */ + @Override + @Nullable + public IBinder onBind(final Intent intent) { + IBinder ret = null; + try { + ret = super.onBind(intent); + } catch (final Exception e) { + Log.d(TAG, "Failed to bind to TileService", e); + } + return ret; + } + + @Override + public void onClick() { + if (tunnel != null) { + unlockAndRun(() -> { + final Tile tile = getQsTile(); + if (tile != null) { + tile.setIcon(tile.getIcon() == iconOn ? iconOff : iconOn); + tile.updateTile(); + } + tunnel.setState(State.TOGGLE).whenComplete((v, t) -> { + if (t == null) { + updateTile(); + } else { + final Intent toggleIntent = new Intent(this, TunnelToggleActivity.class); + toggleIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(toggleIntent); + } + }); + }); + } else { + final Intent intent = new Intent(this, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivityAndCollapse(intent); + } + } + + @Override + public void onCreate() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + iconOff = iconOn = Icon.createWithResource(this, R.drawable.ic_tile); + return; + } + final SlashDrawable icon = new SlashDrawable(getResources().getDrawable(R.drawable.ic_tile, Application.get().getTheme())); + icon.setAnimationEnabled(false); /* Unfortunately we can't have animations, since Icons are marshaled. */ + icon.setSlashed(false); + Bitmap b = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(b); + icon.setBounds(0, 0, c.getWidth(), c.getHeight()); + icon.draw(c); + iconOn = Icon.createWithBitmap(b); + icon.setSlashed(true); + b = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + c = new Canvas(b); + icon.setBounds(0, 0, c.getWidth(), c.getHeight()); + icon.draw(c); + iconOff = Icon.createWithBitmap(b); + } + + @Override + public void onStartListening() { + Application.getTunnelManager().addOnPropertyChangedCallback(onTunnelChangedCallback); + if (tunnel != null) + tunnel.addOnPropertyChangedCallback(onStateChangedCallback); + updateTile(); + } + + @Override + public void onStopListening() { + if (tunnel != null) + tunnel.removeOnPropertyChangedCallback(onStateChangedCallback); + Application.getTunnelManager().removeOnPropertyChangedCallback(onTunnelChangedCallback); + } + + private void updateTile() { + // Update the tunnel. + final ObservableTunnel newTunnel = Application.getTunnelManager().getLastUsedTunnel(); + if (newTunnel != tunnel) { + if (tunnel != null) + tunnel.removeOnPropertyChangedCallback(onStateChangedCallback); + tunnel = newTunnel; + if (tunnel != null) + tunnel.addOnPropertyChangedCallback(onStateChangedCallback); + } + // Update the tile contents. + final String label; + final int state; + final Tile tile = getQsTile(); + if (tunnel != null) { + label = tunnel.getName(); + state = tunnel.getState() == State.UP ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; + } else { + label = getString(R.string.app_name); + state = Tile.STATE_INACTIVE; + } + if (tile == null) + return; + tile.setLabel(label); + if (tile.getState() != state) { + tile.setIcon(state == Tile.STATE_ACTIVE ? iconOn : iconOff); + tile.setState(state); + } + tile.updateTile(); + } + + private final class OnStateChangedCallback extends OnPropertyChangedCallback { + @Override + public void onPropertyChanged(final Observable sender, final int propertyId) { + if (!Objects.equals(sender, tunnel)) { + sender.removeOnPropertyChangedCallback(this); + return; + } + if (propertyId != 0 && propertyId != BR.state) + return; + updateTile(); + } + } + + private final class OnTunnelChangedCallback extends OnPropertyChangedCallback { + @Override + public void onPropertyChanged(final Observable sender, final int propertyId) { + if (propertyId != 0 && propertyId != BR.lastUsedTunnel) + return; + updateTile(); + } + } +} |