diff options
15 files changed, 557 insertions, 2 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java b/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java index 89aeb4be..024bfaba 100644 --- a/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java +++ b/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java @@ -45,6 +45,7 @@ public class DrawerSettingsAdapter extends BaseAdapter { public static final int BATTERY_SAVER = 3; public static final int ALWAYS_ON = 4; public static final int DONATE = 5; + public static final int SELECT_APPS = 6; //view types public final static int VIEW_SIMPLE_TEXT = 0; diff --git a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java index 640c143a..21bc8360 100644 --- a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java @@ -59,6 +59,8 @@ import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.fragments.AboutFragment; import se.leap.bitmaskclient.fragments.AlwaysOnDialog; import se.leap.bitmaskclient.fragments.LogFragment; +import se.leap.bitmaskclient.fragments.SelectAppsFragment; +import se.leap.bitmaskclient.fragments.Settings_Allowed_Apps; import static android.content.Context.MODE_PRIVATE; import static se.leap.bitmaskclient.BitmaskApp.getRefWatcher; @@ -74,6 +76,7 @@ import static se.leap.bitmaskclient.DrawerSettingsAdapter.DONATE; import static se.leap.bitmaskclient.DrawerSettingsAdapter.DrawerSettingsItem.getSimpleTextInstance; import static se.leap.bitmaskclient.DrawerSettingsAdapter.DrawerSettingsItem.getSwitchInstance; import static se.leap.bitmaskclient.DrawerSettingsAdapter.LOG; +import static se.leap.bitmaskclient.DrawerSettingsAdapter.SELECT_APPS; import static se.leap.bitmaskclient.DrawerSettingsAdapter.SWITCH_PROVIDER; import static se.leap.bitmaskclient.R.string.about_fragment_title; import static se.leap.bitmaskclient.R.string.donate_title; @@ -273,6 +276,7 @@ public class NavigationDrawerFragment extends Fragment { settingsListAdapter.addItem(getSimpleTextInstance(getContext(), getString(donate_title), R.drawable.ic_donate_36, DONATE)); } settingsListAdapter.addItem(getSimpleTextInstance(getContext(), getString(about_fragment_title), R.drawable.ic_about_36, ABOUT)); + settingsListAdapter.addItem(getSimpleTextInstance(getContext(), getString(about_fragment_title), R.drawable.ic_about_36, SELECT_APPS)); } private ActionBar setupActionBar() { @@ -494,6 +498,10 @@ public class NavigationDrawerFragment extends Fragment { Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(DONATION_URL)); startActivity(browserIntent); break; + case SELECT_APPS: + fragment = new Settings_Allowed_Apps(); + setActionBarTitle(about_fragment_title); + break; default: break; } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java index 55ade1ae..77666378 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -16,6 +16,9 @@ */ package se.leap.bitmaskclient.eip; +import android.content.Context; +import android.content.SharedPreferences; + import com.google.gson.Gson; import org.json.JSONException; @@ -23,9 +26,12 @@ import org.json.JSONObject; import java.io.IOException; import java.io.StringReader; +import java.util.HashSet; +import java.util.Set; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; +import se.leap.bitmaskclient.BitmaskApp; /** * Gateway provides objects defining gateways and their metadata. @@ -52,7 +58,7 @@ public class Gateway { * Build a gateway object from a JSON OpenVPN gateway definition in eip-service.json * and create a VpnProfile belonging to it. */ - public Gateway(JSONObject eip_definition, JSONObject secrets, JSONObject gateway) { + public Gateway(JSONObject eip_definition, JSONObject secrets, JSONObject gateway, Context context) { this.gateway = gateway; this.secrets = secrets; @@ -62,7 +68,11 @@ public class Gateway { mName = locationAsName(eip_definition); mVpnProfile = createVPNProfile(); + System.out.println("###########" + mName + "###########"); mVpnProfile.mName = mName; + + SharedPreferences allow_apps = context.getSharedPreferences("BITMASK", Context.MODE_MULTI_PROCESS); + mVpnProfile.mAllowedAppsVpn = new HashSet<String>(allow_apps.getStringSet("ALLOW_APPS", new HashSet<String>())); } private JSONObject getGeneralConfiguration(JSONObject eip_definition) { diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java index 060843fd..8edf81ef 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -93,7 +93,7 @@ public class GatewaysManager { JSONObject gw = gatewaysDefined.getJSONObject(i); if (isOpenVpnGateway(gw)) { JSONObject secrets = secretsConfiguration(); - Gateway aux = new Gateway(eipDefinition, secrets, gw); + Gateway aux = new Gateway(eipDefinition, secrets, gw, context); if (!gateways.contains(aux)) { addGateway(aux); } diff --git a/app/src/main/java/se/leap/bitmaskclient/fragments/Settings_Allowed_Apps.java b/app/src/main/java/se/leap/bitmaskclient/fragments/Settings_Allowed_Apps.java new file mode 100644 index 00000000..f930b278 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/fragments/Settings_Allowed_Apps.java @@ -0,0 +1,364 @@ +/* + * Copyright (c) 2012-2016 Arne Schwabe + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + */ + +package se.leap.bitmaskclient.fragments; + +import android.Manifest; +import android.app.Activity; +import android.content.SharedPreferences; +import android.support.v4.app.Fragment; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.CompoundButton; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.SearchView; +import android.widget.Switch; +import android.widget.TextView; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.Vector; + +import de.blinkt.openvpn.VpnProfile; +import se.leap.bitmaskclient.EipSetupListener; +import se.leap.bitmaskclient.EipSetupObserver; +import se.leap.bitmaskclient.R; + +/** + * Created by arne on 16.11.14. + */ +public class Settings_Allowed_Apps extends Fragment implements AdapterView.OnItemClickListener, CompoundButton.OnCheckedChangeListener, View.OnClickListener { + private ListView mListView; + private VpnProfile mProfile; + private TextView mDefaultAllowTextView; + private PackageAdapter mListAdapter; + + private SharedPreferences allow_apps; + private SharedPreferences.Editor allow_apps_editor; + private Set<String> apps; + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + AppViewHolder avh = (AppViewHolder) view.getTag(); + avh.checkBox.toggle(); + } + + @Override + public void onClick(View v) { + + } + + static class AppViewHolder { + public ApplicationInfo mInfo; + public View rootView; + public TextView appName; + public ImageView appIcon; + //public TextView appSize; + //public TextView disabled; + public CompoundButton checkBox; + + static public AppViewHolder createOrRecycle(LayoutInflater inflater, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = inflater.inflate(R.layout.allowed_application_layout, parent, false); + + // Creates a ViewHolder and store references to the two children views + // we want to bind data to. + AppViewHolder holder = new AppViewHolder(); + holder.rootView = convertView; + holder.appName = (TextView) convertView.findViewById(R.id.app_name); + holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon); + //holder.appSize = (TextView) convertView.findViewById(R.id.app_size); + //holder.disabled = (TextView) convertView.findViewById(R.id.app_disabled); + holder.checkBox = (CompoundButton) convertView.findViewById(R.id.app_selected); + convertView.setTag(holder); + + return holder; + } else { + // Get the ViewHolder back to get fast access to the TextView + // and the ImageView. + return (AppViewHolder) convertView.getTag(); + } + } + + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + String packageName = (String) buttonView.getTag(); + + if (isChecked) { + Log.d("openvpn", "adding to allowed apps" + packageName); + + apps.add(packageName); + + } else { + Log.d("openvpn", "removing from allowed apps" + packageName); + //Log.d("eneko", this.getContext().getSharedPreferences("BITMASK", Context.MODE_MULTI_PROCESS).getString("ALLOW_APPS", "")); + + apps.remove(packageName); + } + allow_apps_editor.clear(); + allow_apps_editor.putStringSet("ALLOW_APPS", apps); + allow_apps_editor.apply(); + } + + + class PackageAdapter extends BaseAdapter implements Filterable { + private Vector<ApplicationInfo> mPackages; + private final LayoutInflater mInflater; + private final PackageManager mPm; + private ItemFilter mFilter = new ItemFilter(); + private Vector<ApplicationInfo> mFilteredData; + + + private class ItemFilter extends Filter { + @Override + protected FilterResults performFiltering(CharSequence constraint) { + + String filterString = constraint.toString().toLowerCase(Locale.getDefault()); + + FilterResults results = new FilterResults(); + + + int count = mPackages.size(); + final Vector<ApplicationInfo> nlist = new Vector<>(count); + + for (int i = 0; i < count; i++) { + ApplicationInfo pInfo = mPackages.get(i); + CharSequence appName = pInfo.loadLabel(mPm); + + if (TextUtils.isEmpty(appName)) + appName = pInfo.packageName; + + if (appName instanceof String) { + if (((String) appName).toLowerCase(Locale.getDefault()).contains(filterString)) + nlist.add(pInfo); + } else { + if (appName.toString().toLowerCase(Locale.getDefault()).contains(filterString)) + nlist.add(pInfo); + } + } + results.values = nlist; + results.count = nlist.size(); + + return results; + } + + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + mFilteredData = (Vector<ApplicationInfo>) results.values; + notifyDataSetChanged(); + } + + } + + + PackageAdapter(Context c, VpnProfile vp) { + mPm = c.getPackageManager(); + mProfile = vp; + mInflater = LayoutInflater.from(c); + + mPackages = new Vector<>(); + mFilteredData = mPackages; + } + + private void populateList(Activity c) { + List<ApplicationInfo> installedPackages = mPm.getInstalledApplications(PackageManager.GET_META_DATA); + + // Remove apps not using Internet + + int androidSystemUid = 0; + ApplicationInfo system = null; + Vector<ApplicationInfo> apps = new Vector<ApplicationInfo>(); + + try { + system = mPm.getApplicationInfo("android", PackageManager.GET_META_DATA); + androidSystemUid = system.uid; + apps.add(system); + } catch (PackageManager.NameNotFoundException e) { + } + + + for (ApplicationInfo app : installedPackages) { + + if (mPm.checkPermission(Manifest.permission.INTERNET, app.packageName) == PackageManager.PERMISSION_GRANTED && + app.uid != androidSystemUid) { + + apps.add(app); + } + } + + Collections.sort(apps, new ApplicationInfo.DisplayNameComparator(mPm)); + mPackages = apps; + mFilteredData = apps; + c.runOnUiThread(new Runnable() { + @Override + public void run() { + notifyDataSetChanged(); + } + }); + } + + @Override + public int getCount() { + return mFilteredData.size(); + } + + @Override + public Object getItem(int position) { + return mFilteredData.get(position); + } + + @Override + public long getItemId(int position) { + return mFilteredData.get(position).packageName.hashCode(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + AppViewHolder viewHolder = AppViewHolder.createOrRecycle(mInflater, convertView, parent); + convertView = viewHolder.rootView; + viewHolder.mInfo = mFilteredData.get(position); + final ApplicationInfo mInfo = mFilteredData.get(position); + + + CharSequence appName = mInfo.loadLabel(mPm); + + if (TextUtils.isEmpty(appName)) + appName = mInfo.packageName; + viewHolder.appName.setText(appName); + viewHolder.appIcon.setImageDrawable(mInfo.loadIcon(mPm)); + viewHolder.checkBox.setTag(mInfo.packageName); + viewHolder.checkBox.setOnCheckedChangeListener(Settings_Allowed_Apps.this); + viewHolder.checkBox.setChecked(apps.contains(mInfo.packageName)); + + + // viewHolder.checkBox.setChecked(mProfile.mAllowedAppsVpn.contains(mInfo.packageName)); + return viewHolder.rootView; + } + + @Override + public Filter getFilter() { + return mFilter; + } + } + + @Override + public void onResume() { + super.onResume(); +// changeDisallowText(mProfile.mAllowedAppsVpnAreDisallowed); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + allow_apps = this.getContext().getSharedPreferences("BITMASK", Context.MODE_MULTI_PROCESS); + allow_apps_editor = allow_apps.edit(); + apps = new HashSet<String>(allow_apps.getStringSet("ALLOW_APPS", new HashSet<String>())); + +// /String profileUuid = getArguments().getString(getActivity().getPackageName() + ".profileUUID"); +// mProfile = EipSetupObserver.getProfile(); +// getActivity().setTitle(getString(R.string.edit_profile_title, mProfile.getName())); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.allowed_apps, menu); + + SearchView searchView = (SearchView) menu.findItem( R.id.app_search_widget ).getActionView(); + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + mListView.setFilterText(query); + mListView.setTextFilterEnabled(true); + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { + mListView.setFilterText(newText); + if (TextUtils.isEmpty(newText)) + mListView.setTextFilterEnabled(false); + else + mListView.setTextFilterEnabled(true); + + return true; + } + }); + searchView.setOnCloseListener(new SearchView.OnCloseListener() { + @Override + public boolean onClose() { + mListView.clearTextFilter(); + mListAdapter.getFilter().filter(""); + return false; + } + }); + + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.allowed_vpn_apps, container, false); + + mDefaultAllowTextView = (TextView) v.findViewById(R.id.default_allow_text); + + Switch vpnOnDefaultSwitch = (Switch) v.findViewById(R.id.default_allow); + + vpnOnDefaultSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + + changeDisallowText(isChecked); + mProfile.mAllowedAppsVpnAreDisallowed = isChecked; + } + }); + +// vpnOnDefaultSwitch.setChecked(mProfile.mAllowedAppsVpnAreDisallowed); + + mListView = (ListView) v.findViewById(android.R.id.list); + + mListAdapter = new PackageAdapter(getActivity(), mProfile); + mListView.setAdapter(mListAdapter); + mListView.setOnItemClickListener(this); + + mListView.setEmptyView(v.findViewById(R.id.loading_container)); + + new Thread(new Runnable() { + @Override + public void run() { + mListAdapter.populateList(getActivity()); + } + }).start(); + + return v; + } + + private void changeDisallowText(boolean selectedAreDisallowed) { + if (selectedAreDisallowed) + mDefaultAllowTextView.setText(R.string.vpn_disallow_radio); + else + mDefaultAllowTextView.setText(R.string.vpn_allow_radio); + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_search_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_search_white_24dp.png Binary files differnew file mode 100755 index 00000000..bbfbc96c --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_search_white_24dp.png diff --git a/app/src/main/res/drawable-mdpi/ic_search_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_search_white_24dp.png Binary files differnew file mode 100755 index 00000000..faefc59c --- /dev/null +++ b/app/src/main/res/drawable-mdpi/ic_search_white_24dp.png diff --git a/app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png Binary files differnew file mode 100755 index 00000000..bfc3e393 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png diff --git a/app/src/main/res/drawable-xxhdpi/bg_switchbar.xml b/app/src/main/res/drawable-xxhdpi/bg_switchbar.xml new file mode 100644 index 00000000..7af57ad3 --- /dev/null +++ b/app/src/main/res/drawable-xxhdpi/bg_switchbar.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (c) 2012-2016 Arne Schwabe + ~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/switchbar" /> +</shape>
\ No newline at end of file diff --git a/app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png Binary files differnew file mode 100755 index 00000000..abbb9895 --- /dev/null +++ b/app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png diff --git a/app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png Binary files differnew file mode 100755 index 00000000..dd5adfc7 --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png diff --git a/app/src/main/res/layout/allowed_application_layout.xml b/app/src/main/res/layout/allowed_application_layout.xml new file mode 100644 index 00000000..d8d846f9 --- /dev/null +++ b/app/src/main/res/layout/allowed_application_layout.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (c) 2012-2016 Arne Schwabe + ~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + --> + +<GridLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:paddingTop="8dip" + android:paddingBottom="8dip" + android:columnCount="4" + tools:ignore="RtlCompat"> + + <ImageView + android:id="@+id/app_icon" + android:layout_width="@android:dimen/app_icon_size" + android:layout_height="@android:dimen/app_icon_size" + android:layout_rowSpan="1" + android:layout_marginEnd="8dip" + android:scaleType="centerInside" + tools:background="@drawable/icon" + android:contentDescription="@null" /> + + <TextView + tools:text="@string/app" + android:id="@+id/app_name" + android:layout_width="0dip" + android:layout_columnSpan="2" + android:layout_rowSpan="1" + android:layout_gravity="fill_horizontal|center_vertical" + android:layout_marginTop="2dip" + android:singleLine="true" + android:ellipsize="marquee" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textAlignment="viewStart" /> + + <CheckBox android:id="@+id/app_selected" + android:layout_marginStart="8dip" + android:layout_gravity="center_vertical" + android:layout_rowSpan="1" + android:visibility="visible" /> + +<!-- <TextView + android:id="@+id/app_size" + android:layout_width="0dip" + android:layout_gravity="fill_horizontal|top" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textAlignment="viewStart" /> --> + + <!-- <TextView + android:id="@+id/app_disabled" + android:layout_marginStart="8dip" + android:layout_gravity="top" + android:textAppearance="?android:attr/textAppearanceSmall" /> --> + +</GridLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/allowed_vpn_apps.xml b/app/src/main/res/layout/allowed_vpn_apps.xml new file mode 100644 index 00000000..0883f4b8 --- /dev/null +++ b/app/src/main/res/layout/allowed_vpn_apps.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (c) 2012-2016 Arne Schwabe + ~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + tools:ignore="RtlCompat" + android:layout_height="match_parent"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/actionBarSize" + android:background="@drawable/bg_switchbar" + android:paddingStart="@dimen/switchbar_pad" + android:elevation="1dp" + android:paddingEnd="@dimen/switchbar_pad" + tools:ignore="NewApi"> + + <Switch + android:id="@+id/default_allow" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + /> + <TextView + android:id="@+id/default_allow_text" + style="@android:style/TextAppearance.Medium.Inverse" + tools:text="@string/vpn_disallow_radio" + android:layout_toStartOf="@id/default_allow" + android:layout_width="wrap_content" + android:layout_centerVertical="true" + android:layout_height="wrap_content" /> + </RelativeLayout> + + <ListView + android:visibility="gone" + android:id="@android:id/list" + android:drawSelectorOnTop="false" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipToPadding="false" + android:scrollbarStyle="outsideOverlay" /> + + + + <LinearLayout + android:id="@+id/loading_container" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="visible" + android:gravity="center"> + + <ProgressBar + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:text="@string/loading" + android:paddingTop="4dip" + android:singleLine="true" /> + + </LinearLayout> + + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/s_layout.xml b/app/src/main/res/layout/s_layout.xml new file mode 100644 index 00000000..d5717a5b --- /dev/null +++ b/app/src/main/res/layout/s_layout.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <Button + android:id="@+id/buttontest" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:text="A"/> + +</ScrollView> diff --git a/app/src/main/res/menu/allowed_apps.xml b/app/src/main/res/menu/allowed_apps.xml new file mode 100644 index 00000000..20eb91e1 --- /dev/null +++ b/app/src/main/res/menu/allowed_apps.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (c) 2012-2015 Arne Schwabe + ~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + --> + +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item + android:id="@+id/app_search_widget" + app:actionViewClass="android.widget.SearchView" + android:icon="@drawable/ic_search_white_24dp" + app:showAsAction="always" + android:title="@string/Search"/> +</menu>
\ No newline at end of file |