1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  * Copyright (C) 2016 Mopria Alliance, Inc.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.bips.ui;
19 
20 import android.annotation.SuppressLint;
21 import android.app.Activity;
22 import android.app.AlertDialog;
23 import android.content.Context;
24 import android.net.Uri;
25 import android.os.Bundle;
26 import android.text.Editable;
27 import android.text.TextWatcher;
28 import android.util.Log;
29 import android.view.KeyEvent;
30 import android.view.View;
31 import android.view.Window;
32 import android.view.WindowManager;
33 import android.view.inputmethod.EditorInfo;
34 import android.view.inputmethod.InputMethodManager;
35 import android.widget.Button;
36 import android.widget.ProgressBar;
37 import android.widget.TextView;
38 
39 import com.android.bips.R;
40 import com.android.bips.discovery.DiscoveredPrinter;
41 import com.android.bips.discovery.ManualDiscovery;
42 
43 import java.util.regex.Matcher;
44 import java.util.regex.Pattern;
45 
46 /**
47  * Allows the user to enter printer address manually
48  */
49 class AddManualPrinterDialog extends AlertDialog implements TextWatcher,
50         TextView.OnEditorActionListener, View.OnKeyListener, ManualDiscovery.PrinterAddCallback {
51     private static final String TAG = AddManualPrinterDialog.class.getSimpleName();
52     private static final boolean DEBUG = false;
53 
54     /**
55      * A regex matching any printer URI with optional protocol, port and path.
56      */
57     private static final String URI_REGEX =
58             "(ipp[s]?://)?[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*(:[0-9]+)?(/.*)?";
59     private static final String FULL_URI_REGEX = "^" + URI_REGEX + "$";
60     private static final Pattern FULL_URI_PATTERN = Pattern.compile(FULL_URI_REGEX);
61 
62     private final ManualDiscovery mDiscovery;
63     private final Activity mActivity;
64     private TextView mHostnameView;
65     private Button mAddButton;
66     private ProgressBar mProgressBar;
67 
AddManualPrinterDialog(Activity activity, ManualDiscovery discovery)68     AddManualPrinterDialog(Activity activity, ManualDiscovery discovery) {
69         super(activity);
70         mDiscovery = discovery;
71         mActivity = activity;
72     }
73 
74     @SuppressLint("InflateParams")
75     @Override
onCreate(Bundle savedInstanceState)76     protected void onCreate(Bundle savedInstanceState) {
77         if (DEBUG) Log.d(TAG, "onCreate");
78         View view = getLayoutInflater().inflate(R.layout.manual_printer_add, null);
79         setView(view);
80         setTitle(R.string.add_printer_by_ip);
81         setButton(AlertDialog.BUTTON_NEGATIVE, getContext().getString(android.R.string.cancel),
82                 (OnClickListener) null);
83         setButton(AlertDialog.BUTTON_POSITIVE, getContext().getString(R.string.add),
84                 (OnClickListener) null);
85 
86         super.onCreate(savedInstanceState);
87         mAddButton = getButton(AlertDialog.BUTTON_POSITIVE);
88         mHostnameView = findViewById(R.id.hostname);
89         mProgressBar = findViewById(R.id.progress);
90 
91         mAddButton.setOnClickListener(view1 -> addPrinter());
92 
93         // Update add button as contents change
94         mHostnameView.addTextChangedListener(this);
95         mHostnameView.setOnEditorActionListener(this);
96         mHostnameView.setOnKeyListener(this);
97 
98         // Force open keyboard if appropriate
99         openKeyboard(mHostnameView);
100 
101         updateButtonState();
102     }
103 
104     @Override
onStop()105     protected void onStop() {
106         if (DEBUG) Log.d(TAG, "onStop()");
107         super.onStop();
108         mDiscovery.cancelAddManualPrinter(this);
109     }
110 
openKeyboard(TextView view)111     private void openKeyboard(TextView view) {
112         Window window = getWindow();
113         if (window != null) {
114             window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
115         }
116 
117         view.requestFocus();
118         InputMethodManager imm = (InputMethodManager) getContext()
119                 .getSystemService(Context.INPUT_METHOD_SERVICE);
120         imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
121     }
122 
updateButtonState()123     private void updateButtonState() {
124         String hostname = mHostnameView.getText().toString();
125         Matcher uriMatcher = FULL_URI_PATTERN.matcher(hostname);
126         mAddButton.setEnabled(uriMatcher.matches());
127     }
128 
129     /** Attempt to add the printer based on current data */
addPrinter()130     private void addPrinter() {
131         // Disable other actions while we are checking
132         mAddButton.setEnabled(false);
133         mHostnameView.setEnabled(false);
134         mProgressBar.setVisibility(View.VISIBLE);
135 
136         // Begin an attempt to add the printer
137         String uriString = mHostnameView.getText().toString();
138         Uri printerUri;
139         if (uriString.contains("://")) {
140             printerUri = Uri.parse(uriString);
141         } else {
142             // create a schemeless URI
143             printerUri = Uri.parse("://" + uriString);
144         }
145         mDiscovery.addManualPrinter(printerUri, this);
146     }
147 
148     @Override
onFound(DiscoveredPrinter printer, boolean supported)149     public void onFound(DiscoveredPrinter printer, boolean supported) {
150         if (supported) {
151             // Success case
152             dismiss();
153             mActivity.finish();
154         } else {
155             error(getContext().getString(R.string.printer_not_supported));
156         }
157     }
158 
159     @Override
onNotFound()160     public void onNotFound() {
161         error(getContext().getString(R.string.no_printer_found));
162     }
163 
164     /** Inform user of error and allow them to correct it */
error(String message)165     private void error(String message) {
166         mProgressBar.setVisibility(View.GONE);
167         mHostnameView.setError(message);
168         mHostnameView.setEnabled(true);
169         openKeyboard(mHostnameView);
170     }
171 
172     @Override
beforeTextChanged(CharSequence charSequence, int start, int count, int after)173     public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
174     }
175 
176     @Override
onTextChanged(CharSequence charSequence, int start, int before, int count)177     public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
178     }
179 
180     @Override
afterTextChanged(Editable editable)181     public void afterTextChanged(Editable editable) {
182         updateButtonState();
183     }
184 
185     @Override
onEditorAction(TextView textView, int id, KeyEvent keyEvent)186     public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
187         if (id == EditorInfo.IME_ACTION_DONE && mAddButton.isEnabled()) {
188             addPrinter();
189             return true;
190         }
191         return false;
192     }
193 
194     @Override
onKey(View view, int keyCode, KeyEvent keyEvent)195     public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
196         if (keyCode == KeyEvent.KEYCODE_ENTER && mAddButton.isEnabled()) {
197             addPrinter();
198             return true;
199         }
200         return false;
201     }
202 }
203