Skip to content

Widgets #181

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# IntelliJ
*.iml
.idea/

# built application files
*.apk
*.ap_
Expand Down
63 changes: 40 additions & 23 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
- FreeOTP
-
- Authors: Nathaniel McCallum <[email protected]>
Expand All @@ -22,71 +21,89 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.fedorahosted.freeotp"
android:versionCode="17"
android:versionName="1.5" >
android:versionName="1.5">

<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="26" />

<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:anyDensity="true"
android:largeScreens="true"
android:xlargeScreens="true"
android:normalScreens="true"
android:resizeable="true"
android:anyDensity="true" />
android:smallScreens="true"
android:xlargeScreens="true" />

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />

<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />

<application
android:allowBackup="false"
android:icon="@drawable/ic_freeotp"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
android:theme="@style/AppTheme">

<activity
android:name=".AboutActivity"
android:theme="@android:style/Theme.Holo.Light.Dialog.NoActionBar"
/>
android:theme="@android:style/Theme.Holo.Light.Dialog.NoActionBar" />

<activity
android:name=".add.ScanActivity"
android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen"
/>
android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" />

<activity
android:name=".edit.DeleteActivity"
android:label="@string/delete_question"
android:theme="@android:style/Theme.Holo.Light.Dialog"
/>
android:theme="@android:style/Theme.Holo.Light.Dialog" />

<activity
android:name=".edit.EditActivity"
android:theme="@android:style/Theme.Holo.Light.Dialog.NoActionBar"
android:windowSoftInputMode="stateVisible"
/>
android:windowSoftInputMode="stateVisible" />

<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:clearTaskOnLaunch="true"
android:launchMode="singleTask"
android:configChanges="orientation|screenSize"
>
android:label="@string/app_name"
android:launchMode="singleTask">
<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="otpauth" android:host="totp" />
<data android:scheme="otpauth" android:host="hotp" />

<data
android:host="totp"
android:scheme="otpauth" />
<data
android:host="hotp"
android:scheme="otpauth" />
</intent-filter>
</activity>

<receiver android:name="org.fedorahosted.freeotp.widget.OtpListWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_info" />
</receiver>
<service
android:name="org.fedorahosted.freeotp.widget.OtpListWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* FreeOTP
*
* Authors: Nathaniel McCallum <[email protected]>
*
* Copyright (C) 2013 Nathaniel McCallum, Red Hat
*
* 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.fedorahosted.freeotp;

import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.widget.Toast;

public class ClipboardManagerUtil {
private ClipboardManagerUtil() {
// no-op
}

/**
* Copies a {@link String} to the clipboard and shows a {@link Toast} informing the user that the code was copied.
*/
public static void copyToClipboard(Context context, ClipboardManager manager, String code) {
manager.setPrimaryClip(ClipData.newPlainText(null, code));
Toast.makeText(context,
R.string.code_copied,
Toast.LENGTH_SHORT).show();
}
}
11 changes: 4 additions & 7 deletions app/src/main/java/org/fedorahosted/freeotp/TokenAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

package org.fedorahosted.freeotp;

import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
Expand All @@ -30,8 +29,6 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.PopupMenu;
import android.widget.Toast;

import org.fedorahosted.freeotp.edit.DeleteActivity;
import org.fedorahosted.freeotp.edit.EditActivity;

Expand Down Expand Up @@ -123,11 +120,11 @@ public void onClick(View v) {
//save token. Image wasn't changed here, so just save it in sync
new TokenPersistence(ctx).save(token);

Context applicationContext = v.getContext().getApplicationContext();
String currentCode = codes.getCurrentCode();

// Copy code to clipboard.
mClipMan.setPrimaryClip(ClipData.newPlainText(null, codes.getCurrentCode()));
Toast.makeText(v.getContext().getApplicationContext(),
R.string.code_copied,
Toast.LENGTH_SHORT).show();
ClipboardManagerUtil.copyToClipboard(applicationContext, mClipMan, currentCode);

mTokenCodes.put(token.getID(), codes);
((TokenLayout) v).start(token.getType(), codes, true);
Expand Down
5 changes: 1 addition & 4 deletions app/src/main/java/org/fedorahosted/freeotp/TokenLayout.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,7 @@ public void bind(Token token, int menu, PopupMenu.OnMenuItemClickListener micl)
mProgressOuter.setVisibility(View.GONE);

// Get the code placeholder.
char[] placeholder = new char[token.getDigits()];
for (int i = 0; i < placeholder.length; i++)
placeholder[i] = '-';
mPlaceholder = new String(placeholder);
mPlaceholder = TokenPlaceholderGenerator.generate(token);

// Show the image.
Picasso.with(getContext())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import org.fedorahosted.freeotp.widget.OtpListWidgetProvider;

public class TokenPersistence {
private static final String NAME = "tokens";
private static final String ORDER = "tokenOrder";
private final SharedPreferences prefs;
private final Gson gson;
private final Context context;

private List<String> getTokenOrder() {
Type type = new TypeToken<List<String>>(){}.getType();
Expand All @@ -60,6 +62,7 @@ private SharedPreferences.Editor setTokenOrder(List<String> order) {
}

public TokenPersistence(Context ctx) {
context = ctx;
prefs = ctx.getApplicationContext().getSharedPreferences(NAME, Context.MODE_PRIVATE);
gson = new Gson();
}
Expand Down Expand Up @@ -102,6 +105,7 @@ public void save(Token token) {
List<String> order = getTokenOrder();
order.add(0, key);
setTokenOrder(order).putString(key, gson.toJson(token)).apply();
OtpListWidgetProvider.notifyWidgetDataChanged(context);
}

public void move(int fromPosition, int toPosition) {
Expand All @@ -116,12 +120,14 @@ public void move(int fromPosition, int toPosition) {

order.add(toPosition, order.remove(fromPosition));
setTokenOrder(order).apply();
OtpListWidgetProvider.notifyWidgetDataChanged(context);
}

public void delete(int position) {
List<String> order = getTokenOrder();
String key = order.remove(position);
setTokenOrder(order).remove(key).apply();
OtpListWidgetProvider.notifyWidgetDataChanged(context);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.fedorahosted.freeotp;

/**
* Generates a placeholder {@link String} to show when
* a {@link Token}'s code should not be shown.
*/
public final class TokenPlaceholderGenerator {
private TokenPlaceholderGenerator(){
// no-op
}

public static String generate(final Token token){
char[] placeholder = new char[token.getDigits()];
for (int i = 0; i < placeholder.length; i++)
placeholder[i] = '-';
return new String(placeholder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* FreeOTP
*
* Authors: Nathaniel McCallum <[email protected]>
*
* Copyright (C) 2013 Nathaniel McCallum, Red Hat
*
* 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.fedorahosted.freeotp.widget;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.widget.RemoteViews;
import org.fedorahosted.freeotp.*;

public class OtpListWidgetProvider extends AppWidgetProvider {

/**
* Should be called whenever the {@link Token}s in the {@link TokenPersistence} are changed.
*/
public static void notifyWidgetDataChanged(final Context context) {
final AppWidgetManager manager = AppWidgetManager.getInstance(context);
final ComponentName providerComponentName = new ComponentName(context, OtpListWidgetProvider.class);
final int[] widgetIds = manager.getAppWidgetIds(providerComponentName);
manager.notifyAppWidgetViewDataChanged(widgetIds, R.id.list_widget);
}

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
for (final int widgetId : appWidgetIds) {
final RemoteViews widget = getFirstWidget(context, widgetId);
appWidgetManager.updateAppWidget(widgetId, widget);
}
}

@Override
public void onReceive(final Context context, final Intent intent) {
super.onReceive(context, intent);
final String action = intent.getAction();
if (OtpListWidgetService.ACTION_SHOW_CODE.equals(action) ||
OtpListWidgetService.ACTION_HIDE_CODE.equals(action)) {
int widgetId = intent.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
final String tokenId = intent.getStringExtra(OtpListWidgetService.EXTRA_TOKEN_ID);
final OtpListWidgetViewModel model = OtpListWidgetViewModel.getInstance(widgetId);
final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

if (OtpListWidgetService.ACTION_SHOW_CODE.equals(action)) {
model.addTokenIdToShow(tokenId);
final ClipboardManager clipboardManager =
(ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
final String code = getCodeForTokenId(context, tokenId);
ClipboardManagerUtil.copyToClipboard(context, clipboardManager, code);
} else {
model.removeTokenIdToShow(tokenId);
}
appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, R.id.list_widget);
}
}

private RemoteViews getFirstWidget(Context context, int widgetId) {
final Intent serviceIntent = new Intent(context, OtpListWidgetService.class)
.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
final RemoteViews widget = new RemoteViews(context.getPackageName(), R.layout.list_widget);
widget.setRemoteAdapter(R.id.list_widget, serviceIntent);
widget.setEmptyView(R.id.list_widget, android.R.id.empty);

setTitleIntent(context, widget);
setCodeClickPendingIntentTemplate(context, widget, widgetId);
return widget;
}

private void setTitleIntent(final Context context, final RemoteViews widget) {
final Intent intent = new Intent(context, MainActivity.class)
.setAction(Intent.ACTION_MAIN);
final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
widget.setOnClickPendingIntent(R.id.widget_title_container, pendingIntent);
}

private void setCodeClickPendingIntentTemplate(Context context, RemoteViews widget, int widgetId) {
final Intent codeClickIntent = new Intent(context, OtpListWidgetProvider.class)
.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
codeClickIntent.setData(Uri.parse(codeClickIntent.toUri(Intent.URI_INTENT_SCHEME)));
final PendingIntent showCodeIntentTemplate =
PendingIntent.getBroadcast(context, 0, codeClickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
widget.setPendingIntentTemplate(R.id.list_widget, showCodeIntentTemplate);
}

private String getCodeForTokenId(final Context context, final String id) {
final TokenPersistence persistence = new TokenPersistence(context);
for (int i = 0; i <= persistence.length(); i++) {
final Token token = persistence.get(i);
if (token.getID().equals(id)) {
return token.generateCodes().getCurrentCode();
}
}
return null;
}
}
Loading